File: SimpleDB.py

package info (click to toggle)
s3cmd 1.1.0~beta3-1
  • links: PTS, VCS
  • area: main
  • in suites: wheezy
  • size: 416 kB
  • sloc: python: 4,332; makefile: 13
file content (175 lines) | stat: -rw-r--r-- 6,903 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
## 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

    ## ------------------------------------------------
    ## Methods implementing SimpleDB API
    ## ------------------------------------------------

    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
        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
    ## ------------------------------------------------

    def send_request(self, *args, **kwargs):
        request = self.create_request(*args, **kwargs)
        #debug("Request: %s" % repr(request))
        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:
            debug("Response: " + str(response))
            raise S3Error(response)

        return response

    def create_request(self, Action, DomainName, parameters = None):
        if not parameters:
            parameters = SortedDict()
        parameters['AWSAccessKeyId'] = self.config.access_key
        parameters['Version'] = self.Version
        parameters['SignatureVersion'] = self.SignatureVersion
        parameters['Action'] = Action
        parameters['Timestamp'] = time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime())
        if DomainName:
            parameters['DomainName'] = DomainName
        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])
        #debug("SignRequest: %s" % h)
        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
        #debug('format_uri(): ' + uri)
        return uri

# vim:et:ts=4:sts=4:ai