S3/SimpleDB.py
d2b144df
 ## Amazon SimpleDB library
 ## Author: Michal Ludvig <michal@logix.cz>
 ##         http://www.logix.cz/michal
 ## License: GPL Version 2
 
 """
 Low-level class for working with Amazon SimpleDB
 """
 
 import time
 import urllib
 import base64
 import hmac
 import sha
 import httplib
 from logging import debug, info, warning, error
 
 from Utils import convertTupleListToDict
 from SortedDict import SortedDict
 from Exceptions import *
 
 class SimpleDB(object):
 	# API Version
 	# See http://docs.amazonwebservices.com/AmazonSimpleDB/2007-11-07/DeveloperGuide/
 	Version = "2007-11-07"
 	SignatureVersion = 1
 
 	def __init__(self, config):
 		self.config = config
 
add6b7fc
 	## ------------------------------------------------
 	## Methods implementing SimpleDB API
 	## ------------------------------------------------
 
d2b144df
 	def ListDomains(self, MaxNumberOfDomains = 100):
 		'''
 		Lists all domains associated with our Access Key. Returns 
 		domain names up to the limit set by MaxNumberOfDomains.
 		'''
 		parameters = SortedDict()
 		parameters['MaxNumberOfDomains'] = MaxNumberOfDomains
add6b7fc
 		return self.send_request("ListDomains", DomainName = None, parameters = parameters)
 
 	def CreateDomain(self, DomainName):
 		return self.send_request("CreateDomain", DomainName = DomainName)
 
 	def DeleteDomain(self, DomainName):
 		return self.send_request("DeleteDomain", DomainName = DomainName)
 
 	def PutAttributes(self, DomainName, ItemName, Attributes):
 		parameters = SortedDict()
 		parameters['ItemName'] = ItemName
 		seq = 0
 		for attrib in Attributes:
 			if type(Attributes[attrib]) == type(list()):
 				for value in Attributes[attrib]:
 					parameters['Attribute.%d.Name' % seq] = attrib
 					parameters['Attribute.%d.Value' % seq] = unicode(value)
 					seq += 1
 			else:
 				parameters['Attribute.%d.Name' % seq] = attrib
 				parameters['Attribute.%d.Value' % seq] = unicode(Attributes[attrib])
 				seq += 1
 		## TODO:
 		## - support for Attribute.N.Replace
 		## - support for multiple values for one attribute
 		return self.send_request("PutAttributes", DomainName = DomainName, parameters = parameters)
 
 	def GetAttributes(self, DomainName, ItemName, Attributes = []):
 		parameters = SortedDict()
 		parameters['ItemName'] = ItemName
 		seq = 0
 		for attrib in Attributes:
 			parameters['AttributeName.%d' % seq] = attrib
 			seq += 1
 		return self.send_request("GetAttributes", DomainName = DomainName, parameters = parameters)
 
 	def DeleteAttributes(self, DomainName, ItemName, Attributes = {}):
 		"""
 		Remove specified Attributes from ItemName.
 		Attributes parameter can be either:
 		- not specified, in which case the whole Item is removed
 		- list, e.g. ['Attr1', 'Attr2'] in which case these parameters are removed
 		- dict, e.g. {'Attr' : 'One', 'Attr' : 'Two'} in which case the 
 		  specified values are removed from multi-value attributes.
 		"""
 		parameters = SortedDict()
 		parameters['ItemName'] = ItemName
 		seq = 0
 		for attrib in Attributes:
 			parameters['Attribute.%d.Name' % seq] = attrib
 			if type(Attributes) == type(dict()):
 				parameters['Attribute.%d.Value' % seq] = unicode(Attributes[attrib])
 			seq += 1
 		return self.send_request("DeleteAttributes", DomainName = DomainName, parameters = parameters)
 
 	def Query(self, DomainName, QueryExpression = None, MaxNumberOfItems = None, NextToken = None):
 		parameters = SortedDict()
 		if QueryExpression:
 			parameters['QueryExpression'] = QueryExpression
 		if MaxNumberOfItems:
 			parameters['MaxNumberOfItems'] = MaxNumberOfItems
 		if NextToken:
 			parameters['NextToken'] = NextToken
 		return self.send_request("Query", DomainName = DomainName, parameters = parameters)
 		## Handle NextToken? Or maybe not - let the upper level do it
 
 	## ------------------------------------------------
 	## Low-level methods for handling SimpleDB requests
 	## ------------------------------------------------
 
d2b144df
 	def send_request(self, *args, **kwargs):
 		request = self.create_request(*args, **kwargs)
add6b7fc
 		#debug("Request: %s" % repr(request))
d2b144df
 		conn = self.get_connection()
 		conn.request("GET", self.format_uri(request['uri_params']))
 		http_response = conn.getresponse()
 		response = {}
 		response["status"] = http_response.status
 		response["reason"] = http_response.reason
 		response["headers"] = convertTupleListToDict(http_response.getheaders())
 		response["data"] =  http_response.read()
 		conn.close()
 
 		if response["status"] < 200 or response["status"] > 299:
add6b7fc
 			debug("Response: " + str(response))
d2b144df
 			raise S3Error(response)
 
 		return response
 
add6b7fc
 	def create_request(self, Action, DomainName, parameters = None):
d2b144df
 		if not parameters:
 			parameters = SortedDict()
 		parameters['AWSAccessKeyId'] = self.config.access_key
 		parameters['Version'] = self.Version
 		parameters['SignatureVersion'] = self.SignatureVersion
add6b7fc
 		parameters['Action'] = Action
d2b144df
 		parameters['Timestamp'] = time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime())
add6b7fc
 		if DomainName:
 			parameters['DomainName'] = DomainName
d2b144df
 		parameters['Signature'] = self.sign_request(parameters)
 		parameters.keys_return_lowercase = False
 		uri_params = urllib.urlencode(parameters)
 		request = {}
 		request['uri_params'] = uri_params
 		request['parameters'] = parameters
 		return request
 
 	def sign_request(self, parameters):
 		h = ""
 		parameters.keys_sort_lowercase = True
 		parameters.keys_return_lowercase = False
 		for key in parameters:
 			h += "%s%s" % (key, parameters[key])
add6b7fc
 		#debug("SignRequest: %s" % h)
d2b144df
 		return base64.encodestring(hmac.new(self.config.secret_key, h, sha).digest()).strip()
 
 	def get_connection(self):
 		if self.config.proxy_host != "":
 			return httplib.HTTPConnection(self.config.proxy_host, self.config.proxy_port)
 		else:
 			if self.config.use_https:
 				return httplib.HTTPSConnection(self.config.simpledb_host)
 			else:
 				return httplib.HTTPConnection(self.config.simpledb_host)
 
 	def format_uri(self, uri_params):
 		if self.config.proxy_host != "":
 			uri = "http://%s/?%s" % (self.config.simpledb_host, uri_params)
 		else:
 			uri = "/?%s" % uri_params
add6b7fc
 		#debug('format_uri(): ' + uri)
d2b144df
 		return uri