Skip to content

Commit

Permalink
progress towards a working requester and connection class to authenti…
Browse files Browse the repository at this point in the history
…cate and connect to the api in general. none of this works i'd bet... work in progress...
  • Loading branch information
adregner committed Apr 27, 2012
1 parent f3f6e3c commit 3331bd9
Show file tree
Hide file tree
Showing 4 changed files with 237 additions and 2 deletions.
37 changes: 35 additions & 2 deletions clouddb/connection.py
Expand Up @@ -9,7 +9,10 @@
This code is licensed under the BSD license. See COPYING for more details.
"""

import os
from urlparse import urlparse

from requester import Requester
import consts

class Connection(object):
"""
Expand All @@ -18,7 +21,7 @@ class Connection(object):
objects that you can use to perform any required action with the service.
"""

def __init__(self, username, api_key, region, auth_url=None):
def __init__(self, username, api_key, region, auth_url=None, **kwargs):
"""
Use this to create your connection to the service with your Rackspace
Cloud user name and API key, and access the Cloud Databases service in
Expand All @@ -27,4 +30,34 @@ def __init__(self, username, api_key, region, auth_url=None):
The auth_url parameter can be used to connect to another compatiable
service endpoint other then Rackspace.
"""
self.user = username
self.key = api_key
self.token = None
self.region = region.upper()

if not auth_url:
auth_url = consts.default_authurl

(scheme, auth_netloc, auth_path, params, query, frag) = urlparse(auth_url)

self.debug = int(kwargs.get('debug', 0))

# this is set twice in here, once to authenticate and another for the
# host the service api endpoint is on
self.client = None

self._authenticate(auth_netloc, auth_path)

def _authenticate(self, auth_host, auth_path):
"""
"""
client = Requester(auth_host)
auth_response = client.request('POST', auth_path, data={
'username': self.user,
'key': self.key
})

def _request(self, method, path, data='', headers=None, params=None):
"""
"""
pass
11 changes: 11 additions & 0 deletions clouddb/consts.py
@@ -0,0 +1,11 @@
# -*- encoding: utf-8 -*-
__author__ = "Andrew Regner <andrew@aregner.com>"

""" See COPYING for license information. """

__version__ = "0.1"
user_agent = "python-clouddb/%s" % __version__
us_authurl = 'https://auth.api.rackspacecloud.com/v1.1'
uk_authurl = 'https://lon.auth.api.rackspacecloud.com/v1.1'
db_management_host = '%s.databases.api.rackspacecloud.com'
default_authurl = us_authurl
31 changes: 31 additions & 0 deletions clouddb/errors.py
@@ -0,0 +1,31 @@
# -*- encoding: utf-8 -*-
__author__ = "Andrew Regner <andrew@aregner.com>"

"""
exception classes
See COPYING for license information.
"""

class Error(StandardError):
"""
Base class for all errors and exceptions
"""
pass


class ResponseError(Error):
"""
Raised when the remote service returns an error.
"""
def __init__(self, status, reason):
self.status = status
self.reason = reason
Error.__init__(self)

def __str__(self):
return '%d: %s' % (self.status, self.reason)

def __repr__(self):
return '%d: %s' % (self.status, self.reason)

160 changes: 160 additions & 0 deletions clouddb/requester.py
@@ -0,0 +1,160 @@
# -*- encoding: utf-8 -*-
__author__ = "Andrew Regner <andrew@aregner.com>"

"""
Generic module to make HTTP(S) requests to a server.
This code is licensed under the BSD license. See COPYING for more details.
"""

from httplib import HTTPSConnection
from urllib import urlencode

try:
from json import loads as json_loads
except ImportError:
from simplejson import loads as json_loads

import errors

class Requester(object):
"""
This class is used internally by the clouddb project to make requests to an
HTTPS server in a convient way.
"""

def __init__(self, host, **kwargs):
"""
"""
self.host = host
self.debug = int(kwargs.get('debug', 0))

self.base_path = "/"
self.base_headers = {}

self._client_setup()
self._authenticate()

def _setup(self):
"""
"""
self.client = HTTPSConnection(self.host)
self.client.set_debuglevel(self.debug)

def _authenticate(self):
"""
"""
pass

def set_base_header(self, header, value):
"""
"""

# set multiple headers
if type(header) in (dict,) and len(header) > 0:
for k, v in header.items():
self.set_header(k, v)

# set this one header, from a list or strings
else:
if type(header) in (tuple, list) and len(header) >= 2:
header, value = header[0], header[1]

self.base_headers[header.title()] = value

def get_base_headers(self):
"""
"""
return self.headers

def delete_base_header(self, header):
"""
"""
if header in self.headers:
del self.base_headers[header.title()]

def _build_path(self, path):
"""
"""
if type(path) in (tuple, list):
path = "/".join(path)
return path.strip('/')

def set_base_path(self, path):
"""
"""
self.base_path = self._build_path(path)

def get_base_path(self):
"""
"""
return self.base_path

def request(self, method='GET', path=None, data=None, headers=None, args=None):
"""
"""
method = method.upper()

# build the full path to request
path = '/' + self.get_base_path() + '/' + self._build_path(path)

# check that we aren't trying to use data when we can't
if data and method not in ('POST', 'PUT'):
raise BadRequest("%s requests cannot contain data" % method)

# merge base headers and supplied headers
headers = self.base_headers.update(headers)

# encode data if needed
if data and type(data) != str:
data = json_dumps(data)
headers['Content-type'] = "application/json"

# don't a type when there is no content
if not data:
del headers['Content-type']

# append url arguments if given
if args:
path = path + '?' + urlencode(args)

# this is how we make a request
def make_request():
self.client.request(method, path, data, headers)
return self.client.getresponse()

try:
# first try...
response = make_request()
except (socket.error, IOError, HTTPException):
# maybe we just lost the socket, try again
self._setup()
response = make_request()

# maybe we need to authenticate again
if response.status == 401:
self._authenticate()
response = make_request()

return response

def get(self, path, data=None, headers=None, args=None):
"""
"""
r = self.request('GET', path, data, headers, args)

if r.status < 200 or r.status > 299:
# TODO : this is probably throwing away some error information
r.read()
raise errors.ResponseError(r.status, r.reason)

read_output = r.read()

if r.getheader('content-type', 'text/plain') == "application/json":
return json_loads(read_output)
else:
return read_output


class BadRequest(Exception):
pass

0 comments on commit 3331bd9

Please sign in to comment.