Permalink
Browse files

Basic project structure.

  • Loading branch information...
0 parents commit d9c9a7a3e5c19ebccfacf4311f05619409a2fa66 @jacobian jacobian committed Apr 12, 2010
Showing with 331 additions and 0 deletions.
  1. +8 −0 .gitignore
  2. +27 −0 LICENSE
  3. +3 −0 MANIFEST.in
  4. 0 README.rst
  5. +1 −0 docs/.gitignore
  6. +15 −0 docs/index.rst
  7. +33 −0 hdcloud/__init__.py
  8. +71 −0 hdcloud/base.py
  9. +54 −0 hdcloud/client.py
  10. +30 −0 hdcloud/exceptions.py
  11. +13 −0 hdcloud/jobs.py
  12. +13 −0 hdcloud/profiles.py
  13. +13 −0 hdcloud/stores.py
  14. +14 −0 setup.cfg
  15. +36 −0 setup.py
  16. 0 tests/__init__.py
@@ -0,0 +1,8 @@
+*.egg-info
+*.pyc
+distribute-*
+cover/
+pip-log.txt
+/.coverage
+/dist
+/build
27 LICENSE
@@ -0,0 +1,27 @@
+Copyright (c) 2009 Jacob Kaplan-Moss
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+ 1. Redistributions of source code must retain the above copyright notice,
+ this list of conditions and the following disclaimer.
+
+ 2. Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+
+ 3. Neither the name of this project nor the names of its contributors may
+ be used to endorse or promote products derived from this software without
+ specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
@@ -0,0 +1,3 @@
+include README.rst
+recursive-include docs *
+recursive-include tests *
No changes.
@@ -0,0 +1 @@
+_build/
@@ -0,0 +1,15 @@
+Python bindings to the HDCloud API
+==================================
+
+Contents:
+
+.. toctree::
+ :maxdepth: 2
+
+Indices and tables
+==================
+
+* :ref:`genindex`
+* :ref:`modindex`
+* :ref:`search`
+
@@ -0,0 +1,33 @@
+from __future__ import absolute_import
+
+__version__ = '1.0a1'
+
+from .client import HDCloudClient
+from .jobs import JobManager
+from .stores import StoreManager
+from .profiles import ProfileManager
+
+class HDCloud(object):
+ """
+ Access to the HDCloud API.
+
+ To use, first create an instance with your creds::
+
+ >>> hdcloud = HDCloud(USERNAME, PASSWORD)
+
+ Then call methods::
+
+ >>> hdcloud.jobs.all()
+ [...]
+
+ >>> hdcloud.jobs.get(id=1)
+ [...]
+
+ Methods are named as documented at http://hdcloud.com/api/v1/help.
+ """
+
+ def __init__(self, username, password):
+ self.client = HDCloudClient(username, password)
+ self.jobs = JobManager(self)
+ self.stores = StoreManager(self)
+ self.profiles = ProfileManager(self)
@@ -0,0 +1,71 @@
+"""
+Base utilities to build API operation managers and objects on top of.
+"""
+
+class Manager(object):
+ """
+ Managers interact with a particular type of resource and provide CRUD
+ operations for them.
+ """
+ resource_class = None
+
+ def __init__(self, api):
+ self.api = api
+
+ def _list(self, url, response_key):
+ resp, body = self.api.client.get(url)
+ if body:
+ return [self.resource_class(self, res) for res in body[response_key]]
+ return body
+
+ def _get(self, url, response_key):
+ resp, body = self.api.client.get(url)
+ return self.resource_class(self, body[response_key])
+
+ def _create(self, url, body, response_key):
+ resp, body = self.api.client.post(url, body=body)
+ return self.resource_class(self, body[response_key])
+
+ def _delete(self, url):
+ resp, body = self.api.client.delete(url)
+
+ def _update(self, url, body):
+ resp, body = self.api.client.put(url, body=body)
+
+class Resource(object):
+ """
+ A resource represents a particular instance of a resource.
+
+ It's pretty much just a bag for attributes.
+ """
+ def __init__(self, manager, info):
+ self.manager = manager
+ self._info = info
+ self._add_details(info)
+
+ def _add_details(self, info):
+ for (k, v) in info.iteritems():
+ setattr(self, k, v)
+
+ def __getattr__(self, k):
+ self.get()
+ if k not in self.__dict__:
+ raise AttributeError(k)
+ else:
+ return self.__dict__[k]
+
+ def __repr__(self):
+ reprkeys = sorted(k for k in self.__dict__.keys() if k[0] != '_' and k != 'manager')
+ info = ", ".join("%s=%s" % (k, getattr(self, k)) for k in reprkeys)
+ return "<%s %s>" % (self.__class__.__name__, info)
+
+ def get(self):
+ new = self.manager.get(self.id)
+ self._add_details(new._info)
+
+ def __eq__(self, other):
+ if not isinstance(other, self.__class__):
+ return False
+ if hasattr(self, 'id') and hasattr(other, 'id'):
+ return self.id == other.id
+ return self._info == other._info
@@ -0,0 +1,54 @@
+import httplib2
+import urlparse
+import urllib
+import hdcloud
+from . import exceptions
+try:
+ import json
+except ImportError:
+ import simplejson as json
+
+class HDCloudClient(httplib2.Http):
+
+ USER_AGENT = 'python-hdcloud/%s' % hdcloud.__version__
+ BASE_URL = 'http://hdcloud.com/api/v1/'
+
+ def __init__(self, username, password):
+ super(HDCloudClient, self).__init__()
+ self.add_credentials(username, password)
+ self.force_exception_to_status_code = True
+
+ def request(self, url, method, *args, **kwargs):
+ url = urlparse.urljoin(self.BASE_URL, url.lstrip('/'))
+
+ # Make sure to hardcode ?format=json
+ parsed = urlparse.urlsplit(url)
+ query = [i for i in urlparse.parse_qsl(parsed.query) if i[0] != 'format']
+ query.append(('format', 'json'))
+ query = urllib.urlencode(query)
+ url = urlparse.urlunsplit((parsed.scheme, parsed.netloc, parsed.path, query, parsed.fragment))
+
+ # Add User-Agent headers
+ kwargs.setdefault('headers', {})
+ kwargs['headers']['User-Agent'] = self.USER_AGENT
+
+ resp, body = super(HDCloudClient, self).request(url, method, *args, **kwargs)
+ body = json.loads(body) if body else None
+
+ if resp.status in (400, 401, 403, 404, 406, 413, 500):
+ raise exceptions.from_response(resp, body)
+
+ return resp, body
+
+ def get(self, url, **kwargs):
+ return self.request(url, 'GET', **kwargs)
+
+ def post(self, url, **kwargs):
+ return self.request(url, 'POST', **kwargs)
+
+ def put(self, url, **kwargs):
+ return self.request(url, 'PUT', **kwargs)
+
+ def delete(self, url, **kwargs):
+ return self.request(url, 'DELETE', **kwargs)
+
@@ -0,0 +1,30 @@
+class HDCloudException(Exception):
+ """
+ The base exception class for all exceptions this library raises.
+ """
+ def __init__(self, code, message=None, details=None):
+ self.code = code
+ self.message = message or self.__class__.message
+ self.details = details
+
+ def __str__(self):
+ return "%s (HTTP %s)" % (self.message, self.code)
+
+_code_map = dict((c.http_status, c) for c in HDCloudException.__subclasses__())
+
+def from_response(response, body):
+ """
+ Return an instance of a HDCloudException or subclass
+ based on an httplib2 response.
+
+ Usage::
+
+ resp, body = http.request(...)
+ if resp.status != 200:
+ raise exception_from_response(resp, body)
+ """
+ cls = _code_map.get(response.status, HDCloudException)
+ if body:
+ return cls(code=response.status, message=body['errors'][0]['message'])
+ else:
+ return cls(code=response.status)
@@ -0,0 +1,13 @@
+from __future__ import absolute_import
+
+from . import base
+
+class Job(base.Resource):
+ def __repr__(self):
+ return "<Job %s>" % self.id
+
+class JobManager(base.Manager):
+ resource_class = Job
+
+ def all(self):
+ return self._list('/jobs', 'jobs')
@@ -0,0 +1,13 @@
+from __future__ import absolute_import
+
+from . import base
+
+class Profile(base.Resource):
+ def __repr__(self):
+ return "<Profile: %s>" % self.name
+
+class ProfileManager(base.Manager):
+ resource_class = Profile
+
+ def list(self):
+ return self._list('/profiles', 'encoding_profiles')
@@ -0,0 +1,13 @@
+from __future__ import absolute_import
+
+from . import base
+
+class Store(base.Resource):
+ def __repr__(self):
+ return "<Store: %s>" % self.name
+
+class StoreManager(base.Manager):
+ resource_class = Store
+
+ def list(self):
+ return self._list('/stores', 'stores')
@@ -0,0 +1,14 @@
+[nosetests]
+with-coverage = true
+cover-package = cloudservers
+cover-html = true
+cover-erase = true
+cover-inclusive = true
+
+[build_sphinx]
+source-dir = docs/
+build-dir = docs/_build
+all_files = 1
+
+[upload_sphinx]
+upload-dir = docs/_build/html
@@ -0,0 +1,36 @@
+import os
+import sys
+from setuptools import setup, find_packages
+
+def read(fname):
+ return open(os.path.join(os.path.dirname(__file__), fname)).read()
+
+requirements = ['httplib2', 'argparse']
+try:
+ import json
+except ImportError:
+ requirements.append('simplejson')
+
+setup(
+ name = "python-hdcloud",
+ version = "1.0a1",
+ description = "Client library for the HD Cloud API",
+ long_description = read('README.rst'),
+ url = 'http://packages.python.org/python-hdcloud',
+ license = 'BSD',
+ author = 'Jacob Kaplan-Moss',
+ author_email = 'jacob@jacobian.org',
+ packages = find_packages(exclude=['tests']),
+ classifiers = [
+ 'Development Status :: 3 - Alpha',
+ 'Environment :: Console',
+ 'Intended Audience :: Developers',
+ 'Intended Audience :: Information Technology',
+ 'License :: OSI Approved :: BSD License',
+ 'Operating System :: OS Independent',
+ 'Programming Language :: Python',
+ ],
+ install_requires = requirements,
+ tests_require = ["nose", "mock"],
+ test_suite = "nose.collector",
+)
No changes.

0 comments on commit d9c9a7a

Please sign in to comment.