Merge lp:~therve/txaws/ebs-support into lp:~txawsteam/txaws/trunk
- ebs-support
- Merge into trunk
Proposed by
Thomas Herve
Status: | Merged | ||||
---|---|---|---|---|---|
Merged at revision: | not available | ||||
Proposed branch: | lp:~therve/txaws/ebs-support | ||||
Merge into: | lp:~txawsteam/txaws/trunk | ||||
Diff against target: | None lines | ||||
To merge this branch: | bzr merge lp:~therve/txaws/ebs-support | ||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Duncan McGreggor | Approve | ||
Review via email: mp+10742@code.launchpad.net |
Commit message
Description of the change
To post a comment you must log in.
Revision history for this message
Thomas Herve (therve) wrote : | # |
Revision history for this message
Duncan McGreggor (oubiwann) wrote : | # |
Looks great -- +1 for merge. Thanks for this new functionality!
review:
Approve
lp:~therve/txaws/ebs-support
updated
- 13. By Thomas Herve
-
Merge separate branches for EBS support.
- 14. By Thomas Herve
-
Add parameter support to describe volumes
- 15. By Thomas Herve
-
Add parameter support to describe snapshots
- 16. By Thomas Herve
-
Merge from trunk
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'txaws/ec2/client.py' | |||
2 | --- txaws/ec2/client.py 2009-08-21 03:26:35 +0000 | |||
3 | +++ txaws/ec2/client.py 2009-08-26 13:50:25 +0000 | |||
4 | @@ -3,7 +3,7 @@ | |||
5 | 3 | 3 | ||
6 | 4 | """EC2 client support.""" | 4 | """EC2 client support.""" |
7 | 5 | 5 | ||
9 | 6 | from base64 import b64encode | 6 | from datetime import datetime |
10 | 7 | from urllib import quote | 7 | from urllib import quote |
11 | 8 | 8 | ||
12 | 9 | from twisted.web.client import getPage | 9 | from twisted.web.client import getPage |
13 | @@ -19,7 +19,7 @@ | |||
14 | 19 | """An Amazon EC2 Reservation. | 19 | """An Amazon EC2 Reservation. |
15 | 20 | 20 | ||
16 | 21 | @attrib reservation_id: Unique ID of the reservation. | 21 | @attrib reservation_id: Unique ID of the reservation. |
18 | 22 | @attrib owner_id: AWS Access Key ID of the user who owns the reservation. | 22 | @attrib owner_id: AWS Access Key ID of the user who owns the reservation. |
19 | 23 | @attrib groups: A list of security groups. | 23 | @attrib groups: A list of security groups. |
20 | 24 | """ | 24 | """ |
21 | 25 | def __init__(self, reservation_id, owner_id, groups=None): | 25 | def __init__(self, reservation_id, owner_id, groups=None): |
22 | @@ -72,11 +72,32 @@ | |||
23 | 72 | self.reservation = reservation | 72 | self.reservation = reservation |
24 | 73 | 73 | ||
25 | 74 | 74 | ||
26 | 75 | class Volume(object): | ||
27 | 76 | """An EBS volume instance.""" | ||
28 | 77 | |||
29 | 78 | def __init__(self, id, size, status, create_time): | ||
30 | 79 | self.id = id | ||
31 | 80 | self.size = size | ||
32 | 81 | self.status = status | ||
33 | 82 | self.create_time = create_time | ||
34 | 83 | self.attachments = [] | ||
35 | 84 | |||
36 | 85 | |||
37 | 86 | class Attachment(object): | ||
38 | 87 | """An attachment of a L{Volume}.""" | ||
39 | 88 | |||
40 | 89 | def __init__(self, instance_id, snapshot_id, availability_zone, status, | ||
41 | 90 | attach_time): | ||
42 | 91 | self.instance_id = instance_id | ||
43 | 92 | self.snapshot_id = snapshot_id | ||
44 | 93 | self.availability_zone = availability_zone | ||
45 | 94 | self.status = status | ||
46 | 95 | self.attach_time = attach_time | ||
47 | 96 | |||
48 | 97 | |||
49 | 75 | class EC2Client(object): | 98 | class EC2Client(object): |
50 | 76 | """A client for EC2.""" | 99 | """A client for EC2.""" |
51 | 77 | 100 | ||
52 | 78 | name_space = '{http://ec2.amazonaws.com/doc/2008-12-01/}' | ||
53 | 79 | |||
54 | 80 | def __init__(self, creds=None, query_factory=None): | 101 | def __init__(self, creds=None, query_factory=None): |
55 | 81 | """Create an EC2Client. | 102 | """Create an EC2Client. |
56 | 82 | 103 | ||
57 | @@ -102,7 +123,7 @@ | |||
58 | 102 | """ | 123 | """ |
59 | 103 | Parse the reservations XML payload that is returned from an AWS | 124 | Parse the reservations XML payload that is returned from an AWS |
60 | 104 | describeInstances API call. | 125 | describeInstances API call. |
62 | 105 | 126 | ||
63 | 106 | Instead of returning the reservations as the "top-most" object, we | 127 | Instead of returning the reservations as the "top-most" object, we |
64 | 107 | return the object that most developers and their code will be | 128 | return the object that most developers and their code will be |
65 | 108 | interested in: the instances. In instances reservation is available on | 129 | interested in: the instances. In instances reservation is available on |
66 | @@ -111,53 +132,38 @@ | |||
67 | 111 | root = XML(xml_bytes) | 132 | root = XML(xml_bytes) |
68 | 112 | results = [] | 133 | results = [] |
69 | 113 | # May be a more elegant way to do this: | 134 | # May be a more elegant way to do this: |
71 | 114 | for reservation_data in root.find(self.name_space + 'reservationSet'): | 135 | for reservation_data in root.find("reservationSet"): |
72 | 115 | # Get the security group information. | 136 | # Get the security group information. |
73 | 116 | groups = [] | 137 | groups = [] |
77 | 117 | for group_data in reservation_data.find( | 138 | for group_data in reservation_data.find("groupSet"): |
78 | 118 | self.name_space + 'groupSet'): | 139 | group_id = group_data.findtext("groupId") |
76 | 119 | group_id = group_data.findtext(self.name_space + 'groupId') | ||
79 | 120 | groups.append(group_id) | 140 | groups.append(group_id) |
80 | 121 | # Create a reservation object with the parsed data. | 141 | # Create a reservation object with the parsed data. |
81 | 122 | reservation = Reservation( | 142 | reservation = Reservation( |
86 | 123 | reservation_id=reservation_data.findtext( | 143 | reservation_id=reservation_data.findtext("reservationId"), |
87 | 124 | self.name_space + 'reservationId'), | 144 | owner_id=reservation_data.findtext("ownerId"), |
84 | 125 | owner_id=reservation_data.findtext( | ||
85 | 126 | self.name_space + 'ownerId'), | ||
88 | 127 | groups=groups) | 145 | groups=groups) |
89 | 128 | # Get the list of instances. | 146 | # Get the list of instances. |
90 | 129 | instances = [] | 147 | instances = [] |
95 | 130 | for instance_data in reservation_data.find( | 148 | for instance_data in reservation_data.find("instancesSet"): |
96 | 131 | self.name_space + 'instancesSet'): | 149 | instance_id = instance_data.findtext("instanceId") |
93 | 132 | instance_id = instance_data.findtext( | ||
94 | 133 | self.name_space + 'instanceId') | ||
97 | 134 | instance_state = instance_data.find( | 150 | instance_state = instance_data.find( |
114 | 135 | self.name_space + 'instanceState').findtext( | 151 | "instanceState").findtext("name") |
115 | 136 | self.name_space + 'name') | 152 | instance_type = instance_data.findtext("instanceType") |
116 | 137 | instance_type = instance_data.findtext( | 153 | image_id = instance_data.findtext("imageId") |
117 | 138 | self.name_space + 'instanceType') | 154 | private_dns_name = instance_data.findtext("privateDnsName") |
118 | 139 | image_id = instance_data.findtext(self.name_space + 'imageId') | 155 | dns_name = instance_data.findtext("dnsName") |
119 | 140 | private_dns_name = instance_data.findtext( | 156 | key_name = instance_data.findtext("keyName") |
120 | 141 | self.name_space + 'privateDnsName') | 157 | ami_launch_index = instance_data.findtext("amiLaunchIndex") |
121 | 142 | dns_name = instance_data.findtext(self.name_space + 'dnsName') | 158 | launch_time = instance_data.findtext("launchTime") |
122 | 143 | key_name = instance_data.findtext(self.name_space + 'keyName') | 159 | placement = instance_data.find("placement").findtext( |
123 | 144 | ami_launch_index = instance_data.findtext( | 160 | "availabilityZone") |
108 | 145 | self.name_space + 'amiLaunchIndex') | ||
109 | 146 | launch_time = instance_data.findtext( | ||
110 | 147 | self.name_space + 'launchTime') | ||
111 | 148 | placement = instance_data.find( | ||
112 | 149 | self.name_space + 'placement').findtext( | ||
113 | 150 | self.name_space + 'availabilityZone') | ||
124 | 151 | products = [] | 161 | products = [] |
129 | 152 | for product_data in instance_data.find( | 162 | for product_data in instance_data.find("productCodesSet"): |
130 | 153 | self.name_space + 'productCodesSet'): | 163 | product_code = product_data.findtext("productCode") |
127 | 154 | product_code = product_data.findtext( | ||
128 | 155 | self.name_space + 'productCode') | ||
131 | 156 | products.append(product_code) | 164 | products.append(product_code) |
136 | 157 | kernel_id = instance_data.findtext( | 165 | kernel_id = instance_data.findtext("kernelId") |
137 | 158 | self.name_space + 'kernelId') | 166 | ramdisk_id = instance_data.findtext("ramdiskId") |
134 | 159 | ramdisk_id = instance_data.findtext( | ||
135 | 160 | self.name_space + 'ramdiskId') | ||
138 | 161 | instance = Instance( | 167 | instance = Instance( |
139 | 162 | instance_id, instance_state, instance_type, image_id, | 168 | instance_id, instance_state, instance_type, image_id, |
140 | 163 | private_dns_name, dns_name, key_name, ami_launch_index, | 169 | private_dns_name, dns_name, key_name, ami_launch_index, |
141 | @@ -169,7 +175,7 @@ | |||
142 | 169 | 175 | ||
143 | 170 | def terminate_instances(self, *instance_ids): | 176 | def terminate_instances(self, *instance_ids): |
144 | 171 | """Terminate some instances. | 177 | """Terminate some instances. |
146 | 172 | 178 | ||
147 | 173 | @param instance_ids: The ids of the instances to terminate. | 179 | @param instance_ids: The ids of the instances to terminate. |
148 | 174 | @return: A deferred which on success gives an iterable of | 180 | @return: A deferred which on success gives an iterable of |
149 | 175 | (id, old-state, new-state) tuples. | 181 | (id, old-state, new-state) tuples. |
150 | @@ -185,17 +191,48 @@ | |||
151 | 185 | root = XML(xml_bytes) | 191 | root = XML(xml_bytes) |
152 | 186 | result = [] | 192 | result = [] |
153 | 187 | # May be a more elegant way to do this: | 193 | # May be a more elegant way to do this: |
162 | 188 | for instance in root.find(self.name_space + 'instancesSet'): | 194 | for instance in root.find("instancesSet"): |
163 | 189 | instanceId = instance.findtext(self.name_space + 'instanceId') | 195 | instanceId = instance.findtext("instanceId") |
164 | 190 | previousState = instance.find( | 196 | previousState = instance.find("previousState").findtext( |
165 | 191 | self.name_space + 'previousState').findtext( | 197 | "name") |
166 | 192 | self.name_space + 'name') | 198 | shutdownState = instance.find("shutdownState").findtext( |
167 | 193 | shutdownState = instance.find( | 199 | "name") |
160 | 194 | self.name_space + 'shutdownState').findtext( | ||
161 | 195 | self.name_space + 'name') | ||
168 | 196 | result.append((instanceId, previousState, shutdownState)) | 200 | result.append((instanceId, previousState, shutdownState)) |
169 | 197 | return result | 201 | return result |
170 | 198 | 202 | ||
171 | 203 | def describe_volumes(self): | ||
172 | 204 | """Describe available volumes.""" | ||
173 | 205 | q = self.query_factory("DescribeVolumes", self.creds) | ||
174 | 206 | d = q.submit() | ||
175 | 207 | return d.addCallback(self._parse_volumes) | ||
176 | 208 | |||
177 | 209 | def _parse_volumes(self, xml_bytes): | ||
178 | 210 | root = XML(xml_bytes) | ||
179 | 211 | result = [] | ||
180 | 212 | for volume_data in root.find("volumeSet"): | ||
181 | 213 | volume_id = volume_data.findtext("volumeId") | ||
182 | 214 | size = int(volume_data.findtext("size")) | ||
183 | 215 | status = volume_data.findtext("status") | ||
184 | 216 | create_time = volume_data.findtext("createTime") | ||
185 | 217 | create_time = datetime.strptime( | ||
186 | 218 | create_time[:19], "%Y-%m-%dT%H:%M:%S") | ||
187 | 219 | volume = Volume(volume_id, size, status, create_time) | ||
188 | 220 | result.append(volume) | ||
189 | 221 | for attachment_data in volume_data.find("attachmentSet"): | ||
190 | 222 | instance_id = attachment_data.findtext("instanceId") | ||
191 | 223 | snapshot_id = attachment_data.findtext("snapshotId") | ||
192 | 224 | availability_zone = attachment_data.findtext( | ||
193 | 225 | "availabilityZone") | ||
194 | 226 | status = attachment_data.findtext("status") | ||
195 | 227 | attach_time = attachment_data.findtext("attachTime") | ||
196 | 228 | attach_time = datetime.strptime( | ||
197 | 229 | attach_time[:19], "%Y-%m-%dT%H:%M:%S") | ||
198 | 230 | attachment = Attachment( | ||
199 | 231 | instance_id, snapshot_id, availability_zone, status, | ||
200 | 232 | attach_time) | ||
201 | 233 | volume.attachments.append(attachment) | ||
202 | 234 | return result | ||
203 | 235 | |||
204 | 199 | 236 | ||
205 | 200 | class Query(object): | 237 | class Query(object): |
206 | 201 | """A query that may be submitted to EC2.""" | 238 | """A query that may be submitted to EC2.""" |
207 | @@ -204,7 +241,7 @@ | |||
208 | 204 | """Create a Query to submit to EC2.""" | 241 | """Create a Query to submit to EC2.""" |
209 | 205 | # Require params (2008-12-01 API): | 242 | # Require params (2008-12-01 API): |
210 | 206 | # Version, SignatureVersion, SignatureMethod, Action, AWSAccessKeyId, | 243 | # Version, SignatureVersion, SignatureMethod, Action, AWSAccessKeyId, |
212 | 207 | # Timestamp || Expires, Signature, | 244 | # Timestamp || Expires, Signature, |
213 | 208 | self.params = {'Version': '2008-12-01', | 245 | self.params = {'Version': '2008-12-01', |
214 | 209 | 'SignatureVersion': '2', | 246 | 'SignatureVersion': '2', |
215 | 210 | 'SignatureMethod': 'HmacSHA1', | 247 | 'SignatureMethod': 'HmacSHA1', |
216 | @@ -242,7 +279,7 @@ | |||
217 | 242 | 279 | ||
218 | 243 | def sign(self): | 280 | def sign(self): |
219 | 244 | """Sign this query using its built in credentials. | 281 | """Sign this query using its built in credentials. |
221 | 245 | 282 | ||
222 | 246 | This prepares it to be sent, and should be done as the last step before | 283 | This prepares it to be sent, and should be done as the last step before |
223 | 247 | submitting the query. Signing is done automatically - this is a public | 284 | submitting the query. Signing is done automatically - this is a public |
224 | 248 | method to facilitate testing. | 285 | method to facilitate testing. |
225 | 249 | 286 | ||
226 | === modified file 'txaws/ec2/tests/test_client.py' | |||
227 | --- txaws/ec2/tests/test_client.py 2009-08-21 03:26:35 +0000 | |||
228 | +++ txaws/ec2/tests/test_client.py 2009-08-26 14:31:01 +0000 | |||
229 | @@ -1,6 +1,7 @@ | |||
230 | 1 | # Copyright (C) 2009 Robert Collins <robertc@robertcollins.net> | 1 | # Copyright (C) 2009 Robert Collins <robertc@robertcollins.net> |
231 | 2 | # Licenced under the txaws licence available at /LICENSE in the txaws source. | 2 | # Licenced under the txaws licence available at /LICENSE in the txaws source. |
232 | 3 | 3 | ||
233 | 4 | from datetime import datetime | ||
234 | 4 | import os | 5 | import os |
235 | 5 | 6 | ||
236 | 6 | from twisted.internet.defer import succeed | 7 | from twisted.internet.defer import succeed |
237 | @@ -86,6 +87,31 @@ | |||
238 | 86 | """ | 87 | """ |
239 | 87 | 88 | ||
240 | 88 | 89 | ||
241 | 90 | sample_describe_volumes_result = """<?xml version="1.0"?> | ||
242 | 91 | <DescribeVolumesResponse xmlns="http://ec2.amazonaws.com/doc/2008-12-01/"> | ||
243 | 92 | <volumeSet> | ||
244 | 93 | <item> | ||
245 | 94 | <volumeId>vol-4282672b</volumeId> | ||
246 | 95 | <size>800</size> | ||
247 | 96 | <status>in-use</status> | ||
248 | 97 | <createTime>2008-05-07T11:51:50.000Z</createTime> | ||
249 | 98 | <attachmentSet> | ||
250 | 99 | <item> | ||
251 | 100 | <volumeId>vol-4282672b</volumeId> | ||
252 | 101 | <instanceId>i-6058a509</instanceId> | ||
253 | 102 | <size>800</size> | ||
254 | 103 | <snapshotId>snap-12345678</snapshotId> | ||
255 | 104 | <availabilityZone>us-east-1a</availabilityZone> | ||
256 | 105 | <status>attached</status> | ||
257 | 106 | <attachTime>2008-05-07T12:51:50.000Z</attachTime> | ||
258 | 107 | </item> | ||
259 | 108 | </attachmentSet> | ||
260 | 109 | </item> | ||
261 | 110 | </volumeSet> | ||
262 | 111 | </DescribeVolumesResponse> | ||
263 | 112 | """ | ||
264 | 113 | |||
265 | 114 | |||
266 | 89 | class ReservationTestCase(TXAWSTestCase): | 115 | class ReservationTestCase(TXAWSTestCase): |
267 | 90 | 116 | ||
268 | 91 | def test_reservation_creation(self): | 117 | def test_reservation_creation(self): |
269 | @@ -118,7 +144,7 @@ | |||
270 | 118 | 144 | ||
271 | 119 | 145 | ||
272 | 120 | class TestEC2Client(TXAWSTestCase): | 146 | class TestEC2Client(TXAWSTestCase): |
274 | 121 | 147 | ||
275 | 122 | def test_init_no_creds(self): | 148 | def test_init_no_creds(self): |
276 | 123 | os.environ['AWS_SECRET_ACCESS_KEY'] = 'foo' | 149 | os.environ['AWS_SECRET_ACCESS_KEY'] = 'foo' |
277 | 124 | os.environ['AWS_ACCESS_KEY_ID'] = 'bar' | 150 | os.environ['AWS_ACCESS_KEY_ID'] = 'bar' |
278 | @@ -160,7 +186,6 @@ | |||
279 | 160 | self.assertEquals(instance.kernel_id, "aki-b51cf9dc") | 186 | self.assertEquals(instance.kernel_id, "aki-b51cf9dc") |
280 | 161 | self.assertEquals(instance.ramdisk_id, "ari-b31cf9da") | 187 | self.assertEquals(instance.ramdisk_id, "ari-b31cf9da") |
281 | 162 | 188 | ||
282 | 163 | |||
283 | 164 | def test_parse_reservation(self): | 189 | def test_parse_reservation(self): |
284 | 165 | ec2 = client.EC2Client(creds='foo') | 190 | ec2 = client.EC2Client(creds='foo') |
285 | 166 | results = ec2._parse_instances(sample_describe_instances_result) | 191 | results = ec2._parse_instances(sample_describe_instances_result) |
286 | @@ -286,3 +311,38 @@ | |||
287 | 286 | query.sign() | 311 | query.sign() |
288 | 287 | self.assertEqual('4hEtLuZo9i6kuG3TOXvRQNOrE/U=', | 312 | self.assertEqual('4hEtLuZo9i6kuG3TOXvRQNOrE/U=', |
289 | 288 | query.params['Signature']) | 313 | query.params['Signature']) |
290 | 314 | |||
291 | 315 | |||
292 | 316 | class TestEBS(TXAWSTestCase): | ||
293 | 317 | |||
294 | 318 | def test_describe_volumes(self): | ||
295 | 319 | |||
296 | 320 | class StubQuery(object): | ||
297 | 321 | def __init__(stub, action, creds): | ||
298 | 322 | self.assertEqual(action, "DescribeVolumes") | ||
299 | 323 | self.assertEqual("foo", creds) | ||
300 | 324 | |||
301 | 325 | def submit(self): | ||
302 | 326 | return succeed(sample_describe_volumes_result) | ||
303 | 327 | |||
304 | 328 | def check_parsed_volumes(volumes): | ||
305 | 329 | self.assertEquals(len(volumes), 1) | ||
306 | 330 | volume = volumes[0] | ||
307 | 331 | self.assertEquals(volume.id, "vol-4282672b") | ||
308 | 332 | self.assertEquals(volume.size, 800) | ||
309 | 333 | self.assertEquals(volume.status, "in-use") | ||
310 | 334 | create_time = datetime(2008, 05, 07, 11, 51, 50) | ||
311 | 335 | self.assertEquals(volume.create_time, create_time) | ||
312 | 336 | self.assertEquals(len(volume.attachments), 1) | ||
313 | 337 | attachment = volume.attachments[0] | ||
314 | 338 | self.assertEquals(attachment.instance_id, "i-6058a509") | ||
315 | 339 | self.assertEquals(attachment.snapshot_id, "snap-12345678") | ||
316 | 340 | self.assertEquals(attachment.availability_zone, "us-east-1a") | ||
317 | 341 | self.assertEquals(attachment.status, "attached") | ||
318 | 342 | attach_time = datetime(2008, 05, 07, 12, 51, 50) | ||
319 | 343 | self.assertEquals(attachment.attach_time, attach_time) | ||
320 | 344 | |||
321 | 345 | ec2 = client.EC2Client(creds="foo", query_factory=StubQuery) | ||
322 | 346 | d = ec2.describe_volumes() | ||
323 | 347 | d.addCallback(check_parsed_volumes) | ||
324 | 348 | return d | ||
325 | 289 | 349 | ||
326 | === modified file 'txaws/storage/client.py' | |||
327 | --- txaws/storage/client.py 2009-08-20 12:15:12 +0000 | |||
328 | +++ txaws/storage/client.py 2009-08-26 13:50:25 +0000 | |||
329 | @@ -19,9 +19,6 @@ | |||
330 | 19 | from txaws.util import XML, calculate_md5 | 19 | from txaws.util import XML, calculate_md5 |
331 | 20 | 20 | ||
332 | 21 | 21 | ||
333 | 22 | name_space = '{http://s3.amazonaws.com/doc/2006-03-01/}' | ||
334 | 23 | |||
335 | 24 | |||
336 | 25 | class S3Request(object): | 22 | class S3Request(object): |
337 | 26 | 23 | ||
338 | 27 | def __init__(self, verb, bucket=None, object_name=None, data='', | 24 | def __init__(self, verb, bucket=None, object_name=None, data='', |
339 | @@ -114,10 +111,10 @@ | |||
340 | 114 | Parse XML bucket list response. | 111 | Parse XML bucket list response. |
341 | 115 | """ | 112 | """ |
342 | 116 | root = XML(response) | 113 | root = XML(response) |
345 | 117 | for bucket in root.find(name_space + 'Buckets'): | 114 | for bucket in root.find("Buckets"): |
346 | 118 | timeText = bucket.findtext(name_space + 'CreationDate') | 115 | timeText = bucket.findtext("CreationDate") |
347 | 119 | yield { | 116 | yield { |
349 | 120 | 'name': bucket.findtext(name_space + 'Name'), | 117 | 'name': bucket.findtext("Name"), |
350 | 121 | 'created': Time.fromISO8601TimeAndDate(timeText), | 118 | 'created': Time.fromISO8601TimeAndDate(timeText), |
351 | 122 | } | 119 | } |
352 | 123 | 120 | ||
353 | 124 | 121 | ||
354 | === modified file 'txaws/util.py' | |||
355 | --- txaws/util.py 2009-08-20 12:15:12 +0000 | |||
356 | +++ txaws/util.py 2009-08-26 13:50:25 +0000 | |||
357 | @@ -9,14 +9,14 @@ | |||
358 | 9 | import hmac | 9 | import hmac |
359 | 10 | import time | 10 | import time |
360 | 11 | 11 | ||
362 | 12 | # Import XML from somwhere; here in one place to prevent duplication. | 12 | # Import XMLTreeBuilder from somwhere; here in one place to prevent duplication. |
363 | 13 | try: | 13 | try: |
365 | 14 | from xml.etree.ElementTree import XML | 14 | from xml.etree.ElementTree import XMLTreeBuilder |
366 | 15 | except ImportError: | 15 | except ImportError: |
371 | 16 | from elementtree.ElementTree import XML | 16 | from elementtree.ElementTree import XMLTreeBuilder |
372 | 17 | 17 | ||
373 | 18 | 18 | ||
374 | 19 | __all__ = ['hmac_sha1', 'iso8601time'] | 19 | __all__ = ["hmac_sha1", "iso8601time", "XML"] |
375 | 20 | 20 | ||
376 | 21 | 21 | ||
377 | 22 | def calculate_md5(data): | 22 | def calculate_md5(data): |
378 | @@ -38,3 +38,17 @@ | |||
379 | 38 | return time.strftime("%Y-%m-%dT%H:%M:%SZ", time_tuple) | 38 | return time.strftime("%Y-%m-%dT%H:%M:%SZ", time_tuple) |
380 | 39 | else: | 39 | else: |
381 | 40 | return time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()) | 40 | return time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()) |
382 | 41 | |||
383 | 42 | |||
384 | 43 | class NamespaceFixXmlTreeBuilder(XMLTreeBuilder): | ||
385 | 44 | |||
386 | 45 | def _fixname(self, key): | ||
387 | 46 | if "}" in key: | ||
388 | 47 | key = key.split("}", 1)[1] | ||
389 | 48 | return key | ||
390 | 49 | |||
391 | 50 | |||
392 | 51 | def XML(text): | ||
393 | 52 | parser = NamespaceFixXmlTreeBuilder() | ||
394 | 53 | parser.feed(text) | ||
395 | 54 | return parser.close() |
This is ready to review in the attached branch. I also took the opportunity to remove the need of namespaces, as it feels useless to me.