1 """A simple Python library for accessing the Windows Azure blob service."""
3 import base64, hashlib, hmac, httplib, os, time, urllib
6 # The version of the Azure API we implement; sent in the x-ms-version header.
7 API_VERSION = '2009-09-19'
10 return urllib.unquote_plus(s)
13 return urllib.quote_plus(s)
15 def xmlGetText(nodelist):
19 if node.nodeType == node.TEXT_NODE:
20 text.append(node.data)
27 return xml.dom.minidom.parseString(s)
29 def buildQueryString(uri, params={}):
30 for (k, v) in params.items():
31 if v is None: continue
32 kv = '%s=%s' % (uri_encode(k), uri_encode(v))
39 def add_auth_headers(headers, method, path, account, key):
40 header_order = ['Content-Encoding', 'Content-Language', 'Content-Length',
41 'Content-MD5', 'Content-Type', 'Date', 'If-Modified-Since',
42 'If-Match', 'If-None-Match', 'If-Unmodified-Since',
45 if not headers.has_key('Date'):
46 headers['Date'] = time.strftime("%a, %d %b %Y %H:%M:%S GMT",
48 if not headers.has_key('x-ms-version'):
49 headers['x-ms-version'] = API_VERSION
51 StringToSign = method + "\n"
52 for h in header_order:
54 StringToSign += headers[h] + "\n"
58 # Add Canonicalized Headers
60 for (k, v) in headers.items():
62 if k.startswith('x-ms-'):
63 canonized.append((k, v))
65 for (k, v) in canonized:
66 StringToSign += "%s:%s\n" % (k, v)
68 resource = "/" + account
72 (path, params) = path.split('?', 1)
73 params = [p.split('=') for p in params.split("&")]
74 params = dict((k.lower(), uri_decode(v)) for (k, v) in params)
76 for k in sorted(params):
77 resource += "\n%s:%s" % (k, params[k])
78 StringToSign += resource
80 h = hmac.new(key, digestmod=hashlib.sha256)
81 h.update(StringToSign)
83 signature = base64.b64encode(h.digest())
84 headers['Authorization'] = "SharedKey %s:%s" % (account, signature)
86 class AzureError(RuntimeError):
87 def __init__(self, response):
88 self.response = response
89 self.details = response.read()
91 class AzureConnection:
92 def __init__(self, account=None, key=None):
94 account = os.environ['AZURE_ACCOUNT_NAME']
95 self.account = account
96 self.host = account + ".blob.core.windows.net"
97 #self.conn = httplib.HTTPConnection(self.host)
100 key = os.environ['AZURE_SECRET_KEY']
101 self.key = base64.b64decode(key)
103 def _make_request(self, path, method='GET', body="", headers={}):
104 headers = headers.copy()
105 headers['Content-Length'] = str(len(body))
107 headers['Content-MD5'] \
108 = base64.b64encode(hashlib.md5(body).digest())
109 add_auth_headers(headers, method, path, self.account, self.key)
111 conn = httplib.HTTPConnection(self.host)
112 conn.request(method, path, body, headers)
113 response = conn.getresponse()
114 if response.status // 100 != 2:
115 raise AzureError(response)
117 #print "Response:", response.status
118 #print "Headers:", response.getheaders()
119 #body = response.read()
121 def list(self, container, prefix=''):
124 path = '/' + container + '?restype=container&comp=list'
125 path = buildQueryString(path, {'prefix': prefix, 'marker': marker})
126 r = self._make_request(path)
127 xml = xmlParse(r.read())
129 blobs = xml.getElementsByTagName('Blob')
131 yield xmlGetText(b.getElementsByTagName('Name'))
133 marker = xmlGetText(xml.getElementsByTagName('NextMarker'))
137 def get(self, container, key):
138 path = "/%s/%s" % (container, key)
139 r = self._make_request(path)
142 def put(self, container, key, value):
143 path = "/%s/%s" % (container, key)
144 r = self._make_request(path, method='PUT', body=value,
145 headers={'x-ms-blob-type': 'BlockBlob'})
147 def delete(self, container, key):
148 path = "/%s/%s" % (container, key)
149 r = self._make_request(path, method='DELETE')
151 if __name__ == '__main__':
152 container = 'bluesky'
153 conn = AzureConnection()
155 print "Container contents:"
156 for k in conn.list(container):
159 conn.put(container, "testkey", "A" * 40)
160 print "Fetch result:", conn.get(container, "testkey")
162 for k in list(iter(conn.list(container))):
163 conn.delete(container, k)