~andrewjbeach/juju-ci-tools/make-local-patcher

« back to all changes in this revision

Viewing changes to azure-sdk-for-python-master/azure/storage/__init__.py

  • Committer: Curtis Hovey
  • Date: 2015-06-16 20:18:37 UTC
  • mto: This revision was merged to the branch mainline in revision 995.
  • Revision ID: curtis@canonical.com-20150616201837-l44eyp22o501g6ee
Ensure the env name is in the config.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
#-------------------------------------------------------------------------
 
2
# Copyright (c) Microsoft.  All rights reserved.
 
3
#
 
4
# Licensed under the Apache License, Version 2.0 (the "License");
 
5
# you may not use this file except in compliance with the License.
 
6
# You may obtain a copy of the License at
 
7
#   http://www.apache.org/licenses/LICENSE-2.0
 
8
#
 
9
# Unless required by applicable law or agreed to in writing, software
 
10
# distributed under the License is distributed on an "AS IS" BASIS,
 
11
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 
12
# See the License for the specific language governing permissions and
 
13
# limitations under the License.
 
14
#--------------------------------------------------------------------------
 
15
import hashlib
 
16
import hmac
 
17
import sys
 
18
import types
 
19
 
 
20
from datetime import datetime
 
21
from xml.dom import minidom
 
22
from azure import (WindowsAzureData,
 
23
                   WindowsAzureError,
 
24
                   METADATA_NS,
 
25
                   xml_escape,
 
26
                   _create_entry,
 
27
                   _decode_base64_to_text,
 
28
                   _decode_base64_to_bytes,
 
29
                   _encode_base64,
 
30
                   _fill_data_minidom,
 
31
                   _fill_instance_element,
 
32
                   _get_child_nodes,
 
33
                   _get_child_nodesNS,
 
34
                   _get_children_from_path,
 
35
                   _get_entry_properties,
 
36
                   _general_error_handler,
 
37
                   _list_of,
 
38
                   _parse_response_for_dict,
 
39
                   _unicode_type,
 
40
                   _ERROR_CANNOT_SERIALIZE_VALUE_TO_ENTITY,
 
41
                   )
 
42
 
 
43
# x-ms-version for storage service.
 
44
X_MS_VERSION = '2012-02-12'
 
45
 
 
46
 
 
47
class EnumResultsBase(object):
 
48
 
 
49
    ''' base class for EnumResults. '''
 
50
 
 
51
    def __init__(self):
 
52
        self.prefix = u''
 
53
        self.marker = u''
 
54
        self.max_results = 0
 
55
        self.next_marker = u''
 
56
 
 
57
 
 
58
class ContainerEnumResults(EnumResultsBase):
 
59
 
 
60
    ''' Blob Container list. '''
 
61
 
 
62
    def __init__(self):
 
63
        EnumResultsBase.__init__(self)
 
64
        self.containers = _list_of(Container)
 
65
 
 
66
    def __iter__(self):
 
67
        return iter(self.containers)
 
68
 
 
69
    def __len__(self):
 
70
        return len(self.containers)
 
71
 
 
72
    def __getitem__(self, index):
 
73
        return self.containers[index]
 
74
 
 
75
 
 
76
class Container(WindowsAzureData):
 
77
 
 
78
    ''' Blob container class. '''
 
79
 
 
80
    def __init__(self):
 
81
        self.name = u''
 
82
        self.url = u''
 
83
        self.properties = Properties()
 
84
        self.metadata = {}
 
85
 
 
86
 
 
87
class Properties(WindowsAzureData):
 
88
 
 
89
    ''' Blob container's properties class. '''
 
90
 
 
91
    def __init__(self):
 
92
        self.last_modified = u''
 
93
        self.etag = u''
 
94
 
 
95
 
 
96
class RetentionPolicy(WindowsAzureData):
 
97
 
 
98
    ''' RetentionPolicy in service properties. '''
 
99
 
 
100
    def __init__(self):
 
101
        self.enabled = False
 
102
        self.__dict__['days'] = None
 
103
 
 
104
    def get_days(self):
 
105
        # convert days to int value
 
106
        return int(self.__dict__['days'])
 
107
 
 
108
    def set_days(self, value):
 
109
        ''' set default days if days is set to empty. '''
 
110
        self.__dict__['days'] = value
 
111
 
 
112
    days = property(fget=get_days, fset=set_days)
 
113
 
 
114
 
 
115
class Logging(WindowsAzureData):
 
116
 
 
117
    ''' Logging class in service properties. '''
 
118
 
 
119
    def __init__(self):
 
120
        self.version = u'1.0'
 
121
        self.delete = False
 
122
        self.read = False
 
123
        self.write = False
 
124
        self.retention_policy = RetentionPolicy()
 
125
 
 
126
 
 
127
class Metrics(WindowsAzureData):
 
128
 
 
129
    ''' Metrics class in service properties. '''
 
130
 
 
131
    def __init__(self):
 
132
        self.version = u'1.0'
 
133
        self.enabled = False
 
134
        self.include_apis = None
 
135
        self.retention_policy = RetentionPolicy()
 
136
 
 
137
 
 
138
class StorageServiceProperties(WindowsAzureData):
 
139
 
 
140
    ''' Storage Service Propeties class. '''
 
141
 
 
142
    def __init__(self):
 
143
        self.logging = Logging()
 
144
        self.metrics = Metrics()
 
145
 
 
146
 
 
147
class AccessPolicy(WindowsAzureData):
 
148
 
 
149
    ''' Access Policy class in service properties. '''
 
150
 
 
151
    def __init__(self, start=u'', expiry=u'', permission='u'):
 
152
        self.start = start
 
153
        self.expiry = expiry
 
154
        self.permission = permission
 
155
 
 
156
 
 
157
class SignedIdentifier(WindowsAzureData):
 
158
 
 
159
    ''' Signed Identifier class for service properties. '''
 
160
 
 
161
    def __init__(self):
 
162
        self.id = u''
 
163
        self.access_policy = AccessPolicy()
 
164
 
 
165
 
 
166
class SignedIdentifiers(WindowsAzureData):
 
167
 
 
168
    ''' SignedIdentifier list. '''
 
169
 
 
170
    def __init__(self):
 
171
        self.signed_identifiers = _list_of(SignedIdentifier)
 
172
 
 
173
    def __iter__(self):
 
174
        return iter(self.signed_identifiers)
 
175
 
 
176
    def __len__(self):
 
177
        return len(self.signed_identifiers)
 
178
 
 
179
    def __getitem__(self, index):
 
180
        return self.signed_identifiers[index]
 
181
 
 
182
 
 
183
class BlobEnumResults(EnumResultsBase):
 
184
 
 
185
    ''' Blob list.'''
 
186
 
 
187
    def __init__(self):
 
188
        EnumResultsBase.__init__(self)
 
189
        self.blobs = _list_of(Blob)
 
190
        self.prefixes = _list_of(BlobPrefix)
 
191
        self.delimiter = ''
 
192
 
 
193
    def __iter__(self):
 
194
        return iter(self.blobs)
 
195
 
 
196
    def __len__(self):
 
197
        return len(self.blobs)
 
198
 
 
199
    def __getitem__(self, index):
 
200
        return self.blobs[index]
 
201
 
 
202
 
 
203
class BlobResult(bytes):
 
204
 
 
205
    def __new__(cls, blob, properties):
 
206
        return bytes.__new__(cls, blob if blob else b'')
 
207
 
 
208
    def __init__(self, blob, properties):
 
209
        self.properties = properties
 
210
 
 
211
 
 
212
class Blob(WindowsAzureData):
 
213
 
 
214
    ''' Blob class. '''
 
215
 
 
216
    def __init__(self):
 
217
        self.name = u''
 
218
        self.snapshot = u''
 
219
        self.url = u''
 
220
        self.properties = BlobProperties()
 
221
        self.metadata = {}
 
222
 
 
223
 
 
224
class BlobProperties(WindowsAzureData):
 
225
 
 
226
    ''' Blob Properties '''
 
227
 
 
228
    def __init__(self):
 
229
        self.last_modified = u''
 
230
        self.etag = u''
 
231
        self.content_length = 0
 
232
        self.content_type = u''
 
233
        self.content_encoding = u''
 
234
        self.content_language = u''
 
235
        self.content_md5 = u''
 
236
        self.xms_blob_sequence_number = 0
 
237
        self.blob_type = u''
 
238
        self.lease_status = u''
 
239
        self.lease_state = u''
 
240
        self.lease_duration = u''
 
241
        self.copy_id = u''
 
242
        self.copy_source = u''
 
243
        self.copy_status = u''
 
244
        self.copy_progress = u''
 
245
        self.copy_completion_time = u''
 
246
        self.copy_status_description = u''
 
247
 
 
248
 
 
249
class BlobPrefix(WindowsAzureData):
 
250
 
 
251
    ''' BlobPrefix in Blob. '''
 
252
 
 
253
    def __init__(self):
 
254
        self.name = ''
 
255
 
 
256
 
 
257
class BlobBlock(WindowsAzureData):
 
258
 
 
259
    ''' BlobBlock class '''
 
260
 
 
261
    def __init__(self, id=None, size=None):
 
262
        self.id = id
 
263
        self.size = size
 
264
 
 
265
 
 
266
class BlobBlockList(WindowsAzureData):
 
267
 
 
268
    ''' BlobBlockList class '''
 
269
 
 
270
    def __init__(self):
 
271
        self.committed_blocks = []
 
272
        self.uncommitted_blocks = []
 
273
 
 
274
 
 
275
class PageRange(WindowsAzureData):
 
276
 
 
277
    ''' Page Range for page blob. '''
 
278
 
 
279
    def __init__(self):
 
280
        self.start = 0
 
281
        self.end = 0
 
282
 
 
283
 
 
284
class PageList(object):
 
285
 
 
286
    ''' Page list for page blob. '''
 
287
 
 
288
    def __init__(self):
 
289
        self.page_ranges = _list_of(PageRange)
 
290
 
 
291
    def __iter__(self):
 
292
        return iter(self.page_ranges)
 
293
 
 
294
    def __len__(self):
 
295
        return len(self.page_ranges)
 
296
 
 
297
    def __getitem__(self, index):
 
298
        return self.page_ranges[index]
 
299
 
 
300
 
 
301
class QueueEnumResults(EnumResultsBase):
 
302
 
 
303
    ''' Queue list'''
 
304
 
 
305
    def __init__(self):
 
306
        EnumResultsBase.__init__(self)
 
307
        self.queues = _list_of(Queue)
 
308
 
 
309
    def __iter__(self):
 
310
        return iter(self.queues)
 
311
 
 
312
    def __len__(self):
 
313
        return len(self.queues)
 
314
 
 
315
    def __getitem__(self, index):
 
316
        return self.queues[index]
 
317
 
 
318
 
 
319
class Queue(WindowsAzureData):
 
320
 
 
321
    ''' Queue class '''
 
322
 
 
323
    def __init__(self):
 
324
        self.name = u''
 
325
        self.url = u''
 
326
        self.metadata = {}
 
327
 
 
328
 
 
329
class QueueMessagesList(WindowsAzureData):
 
330
 
 
331
    ''' Queue message list. '''
 
332
 
 
333
    def __init__(self):
 
334
        self.queue_messages = _list_of(QueueMessage)
 
335
 
 
336
    def __iter__(self):
 
337
        return iter(self.queue_messages)
 
338
 
 
339
    def __len__(self):
 
340
        return len(self.queue_messages)
 
341
 
 
342
    def __getitem__(self, index):
 
343
        return self.queue_messages[index]
 
344
 
 
345
 
 
346
class QueueMessage(WindowsAzureData):
 
347
 
 
348
    ''' Queue message class. '''
 
349
 
 
350
    def __init__(self):
 
351
        self.message_id = u''
 
352
        self.insertion_time = u''
 
353
        self.expiration_time = u''
 
354
        self.pop_receipt = u''
 
355
        self.time_next_visible = u''
 
356
        self.dequeue_count = u''
 
357
        self.message_text = u''
 
358
 
 
359
 
 
360
class Entity(WindowsAzureData):
 
361
 
 
362
    ''' Entity class. The attributes of entity will be created dynamically. '''
 
363
    pass
 
364
 
 
365
 
 
366
class EntityProperty(WindowsAzureData):
 
367
 
 
368
    ''' Entity property. contains type and value.  '''
 
369
 
 
370
    def __init__(self, type=None, value=None):
 
371
        self.type = type
 
372
        self.value = value
 
373
 
 
374
 
 
375
class Table(WindowsAzureData):
 
376
 
 
377
    ''' Only for intellicens and telling user the return type. '''
 
378
    pass
 
379
 
 
380
 
 
381
def _parse_blob_enum_results_list(response):
 
382
    respbody = response.body
 
383
    return_obj = BlobEnumResults()
 
384
    doc = minidom.parseString(respbody)
 
385
 
 
386
    for enum_results in _get_child_nodes(doc, 'EnumerationResults'):
 
387
        for child in _get_children_from_path(enum_results, 'Blobs', 'Blob'):
 
388
            return_obj.blobs.append(_fill_instance_element(child, Blob))
 
389
 
 
390
        for child in _get_children_from_path(enum_results,
 
391
                                             'Blobs',
 
392
                                             'BlobPrefix'):
 
393
            return_obj.prefixes.append(
 
394
                _fill_instance_element(child, BlobPrefix))
 
395
 
 
396
        for name, value in vars(return_obj).items():
 
397
            if name == 'blobs' or name == 'prefixes':
 
398
                continue
 
399
            value = _fill_data_minidom(enum_results, name, value)
 
400
            if value is not None:
 
401
                setattr(return_obj, name, value)
 
402
 
 
403
    return return_obj
 
404
 
 
405
 
 
406
def _update_storage_header(request):
 
407
    ''' add additional headers for storage request. '''
 
408
    if request.body:
 
409
        assert isinstance(request.body, bytes)
 
410
 
 
411
    # if it is PUT, POST, MERGE, DELETE, need to add content-lengt to header.
 
412
    if request.method in ['PUT', 'POST', 'MERGE', 'DELETE']:
 
413
        request.headers.append(('Content-Length', str(len(request.body))))
 
414
 
 
415
    # append addtional headers base on the service
 
416
    request.headers.append(('x-ms-version', X_MS_VERSION))
 
417
 
 
418
    # append x-ms-meta name, values to header
 
419
    for name, value in request.headers:
 
420
        if 'x-ms-meta-name-values' in name and value:
 
421
            for meta_name, meta_value in value.items():
 
422
                request.headers.append(('x-ms-meta-' + meta_name, meta_value))
 
423
            request.headers.remove((name, value))
 
424
            break
 
425
    return request
 
426
 
 
427
 
 
428
def _update_storage_blob_header(request, account_name, account_key):
 
429
    ''' add additional headers for storage blob request. '''
 
430
 
 
431
    request = _update_storage_header(request)
 
432
    current_time = datetime.utcnow().strftime('%a, %d %b %Y %H:%M:%S GMT')
 
433
    request.headers.append(('x-ms-date', current_time))
 
434
    request.headers.append(
 
435
        ('Content-Type', 'application/octet-stream Charset=UTF-8'))
 
436
    request.headers.append(('Authorization',
 
437
                            _sign_storage_blob_request(request,
 
438
                                                       account_name,
 
439
                                                       account_key)))
 
440
 
 
441
    return request.headers
 
442
 
 
443
 
 
444
def _update_storage_queue_header(request, account_name, account_key):
 
445
    ''' add additional headers for storage queue request. '''
 
446
    return _update_storage_blob_header(request, account_name, account_key)
 
447
 
 
448
 
 
449
def _update_storage_table_header(request):
 
450
    ''' add additional headers for storage table request. '''
 
451
 
 
452
    request = _update_storage_header(request)
 
453
    for name, _ in request.headers:
 
454
        if name.lower() == 'content-type':
 
455
            break
 
456
    else:
 
457
        request.headers.append(('Content-Type', 'application/atom+xml'))
 
458
    request.headers.append(('DataServiceVersion', '2.0;NetFx'))
 
459
    request.headers.append(('MaxDataServiceVersion', '2.0;NetFx'))
 
460
    current_time = datetime.utcnow().strftime('%a, %d %b %Y %H:%M:%S GMT')
 
461
    request.headers.append(('x-ms-date', current_time))
 
462
    request.headers.append(('Date', current_time))
 
463
    return request.headers
 
464
 
 
465
 
 
466
def _sign_storage_blob_request(request, account_name, account_key):
 
467
    '''
 
468
    Returns the signed string for blob request which is used to set
 
469
    Authorization header. This is also used to sign queue request.
 
470
    '''
 
471
 
 
472
    uri_path = request.path.split('?')[0]
 
473
 
 
474
    # method to sign
 
475
    string_to_sign = request.method + '\n'
 
476
 
 
477
    # get headers to sign
 
478
    headers_to_sign = [
 
479
        'content-encoding', 'content-language', 'content-length',
 
480
        'content-md5', 'content-type', 'date', 'if-modified-since',
 
481
        'if-match', 'if-none-match', 'if-unmodified-since', 'range']
 
482
 
 
483
    request_header_dict = dict((name.lower(), value)
 
484
                               for name, value in request.headers if value)
 
485
    string_to_sign += '\n'.join(request_header_dict.get(x, '')
 
486
                                for x in headers_to_sign) + '\n'
 
487
 
 
488
    # get x-ms header to sign
 
489
    x_ms_headers = []
 
490
    for name, value in request.headers:
 
491
        if 'x-ms' in name:
 
492
            x_ms_headers.append((name.lower(), value))
 
493
    x_ms_headers.sort()
 
494
    for name, value in x_ms_headers:
 
495
        if value:
 
496
            string_to_sign += ''.join([name, ':', value, '\n'])
 
497
 
 
498
    # get account_name and uri path to sign
 
499
    string_to_sign += '/' + account_name + uri_path
 
500
 
 
501
    # get query string to sign if it is not table service
 
502
    query_to_sign = request.query
 
503
    query_to_sign.sort()
 
504
 
 
505
    current_name = ''
 
506
    for name, value in query_to_sign:
 
507
        if value:
 
508
            if current_name != name:
 
509
                string_to_sign += '\n' + name + ':' + value
 
510
            else:
 
511
                string_to_sign += '\n' + ',' + value
 
512
 
 
513
    # sign the request
 
514
    auth_string = 'SharedKey ' + account_name + ':' + \
 
515
        _sign_string(account_key, string_to_sign)
 
516
    return auth_string
 
517
 
 
518
 
 
519
def _sign_storage_table_request(request, account_name, account_key):
 
520
    uri_path = request.path.split('?')[0]
 
521
 
 
522
    string_to_sign = request.method + '\n'
 
523
    headers_to_sign = ['content-md5', 'content-type', 'date']
 
524
    request_header_dict = dict((name.lower(), value)
 
525
                               for name, value in request.headers if value)
 
526
    string_to_sign += '\n'.join(request_header_dict.get(x, '')
 
527
                                for x in headers_to_sign) + '\n'
 
528
 
 
529
    # get account_name and uri path to sign
 
530
    string_to_sign += ''.join(['/', account_name, uri_path])
 
531
 
 
532
    for name, value in request.query:
 
533
        if name == 'comp' and uri_path == '/':
 
534
            string_to_sign += '?comp=' + value
 
535
            break
 
536
 
 
537
    # sign the request
 
538
    auth_string = 'SharedKey ' + account_name + ':' + \
 
539
        _sign_string(account_key, string_to_sign)
 
540
    return auth_string
 
541
 
 
542
 
 
543
def _sign_string(account_key, string_to_sign):
 
544
    decoded_account_key = _decode_base64_to_bytes(account_key)
 
545
    if isinstance(string_to_sign, _unicode_type):
 
546
        string_to_sign = string_to_sign.encode('utf-8')
 
547
    signed_hmac_sha256 = hmac.HMAC(
 
548
        decoded_account_key, string_to_sign, hashlib.sha256)
 
549
    digest = signed_hmac_sha256.digest()
 
550
    encoded_digest = _encode_base64(digest)
 
551
    return encoded_digest
 
552
 
 
553
 
 
554
def _to_python_bool(value):
 
555
    if value.lower() == 'true':
 
556
        return True
 
557
    return False
 
558
 
 
559
 
 
560
def _to_entity_int(data):
 
561
    int_max = (2 << 30) - 1
 
562
    if data > (int_max) or data < (int_max + 1) * (-1):
 
563
        return 'Edm.Int64', str(data)
 
564
    else:
 
565
        return 'Edm.Int32', str(data)
 
566
 
 
567
 
 
568
def _to_entity_bool(value):
 
569
    if value:
 
570
        return 'Edm.Boolean', 'true'
 
571
    return 'Edm.Boolean', 'false'
 
572
 
 
573
 
 
574
def _to_entity_datetime(value):
 
575
    return 'Edm.DateTime', value.strftime('%Y-%m-%dT%H:%M:%S')
 
576
 
 
577
 
 
578
def _to_entity_float(value):
 
579
    return 'Edm.Double', str(value)
 
580
 
 
581
 
 
582
def _to_entity_property(value):
 
583
    if value.type == 'Edm.Binary':
 
584
        return value.type, _encode_base64(value.value)
 
585
 
 
586
    return value.type, str(value.value)
 
587
 
 
588
 
 
589
def _to_entity_none(value):
 
590
    return None, None
 
591
 
 
592
 
 
593
def _to_entity_str(value):
 
594
    return 'Edm.String', value
 
595
 
 
596
 
 
597
# Tables of conversions to and from entity types.  We support specific
 
598
# datatypes, and beyond that the user can use an EntityProperty to get
 
599
# custom data type support.
 
600
 
 
601
def _from_entity_binary(value):
 
602
    return EntityProperty('Edm.Binary', _decode_base64_to_bytes(value))
 
603
 
 
604
 
 
605
def _from_entity_int(value):
 
606
    return int(value)
 
607
 
 
608
 
 
609
def _from_entity_datetime(value):
 
610
    format = '%Y-%m-%dT%H:%M:%S'
 
611
    if '.' in value:
 
612
        format = format + '.%f'
 
613
    if value.endswith('Z'):
 
614
        format = format + 'Z'
 
615
    return datetime.strptime(value, format)
 
616
 
 
617
_ENTITY_TO_PYTHON_CONVERSIONS = {
 
618
    'Edm.Binary': _from_entity_binary,
 
619
    'Edm.Int32': _from_entity_int,
 
620
    'Edm.Int64': _from_entity_int,
 
621
    'Edm.Double': float,
 
622
    'Edm.Boolean': _to_python_bool,
 
623
    'Edm.DateTime': _from_entity_datetime,
 
624
}
 
625
 
 
626
# Conversion from Python type to a function which returns a tuple of the
 
627
# type string and content string.
 
628
_PYTHON_TO_ENTITY_CONVERSIONS = {
 
629
    int: _to_entity_int,
 
630
    bool: _to_entity_bool,
 
631
    datetime: _to_entity_datetime,
 
632
    float: _to_entity_float,
 
633
    EntityProperty: _to_entity_property,
 
634
    str: _to_entity_str,
 
635
}
 
636
 
 
637
if sys.version_info < (3,):
 
638
    _PYTHON_TO_ENTITY_CONVERSIONS.update({
 
639
        long: _to_entity_int,
 
640
        types.NoneType: _to_entity_none,
 
641
        unicode: _to_entity_str,
 
642
    })
 
643
 
 
644
 
 
645
def _convert_entity_to_xml(source):
 
646
    ''' Converts an entity object to xml to send.
 
647
 
 
648
    The entity format is:
 
649
    <entry xmlns:d="http://schemas.microsoft.com/ado/2007/08/dataservices" xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata" xmlns="http://www.w3.org/2005/Atom">
 
650
      <title />
 
651
      <updated>2008-09-18T23:46:19.3857256Z</updated>
 
652
      <author>
 
653
        <name />
 
654
      </author>
 
655
      <id />
 
656
      <content type="application/xml">
 
657
        <m:properties>
 
658
          <d:Address>Mountain View</d:Address>
 
659
          <d:Age m:type="Edm.Int32">23</d:Age>
 
660
          <d:AmountDue m:type="Edm.Double">200.23</d:AmountDue>
 
661
          <d:BinaryData m:type="Edm.Binary" m:null="true" />
 
662
          <d:CustomerCode m:type="Edm.Guid">c9da6455-213d-42c9-9a79-3e9149a57833</d:CustomerCode>
 
663
          <d:CustomerSince m:type="Edm.DateTime">2008-07-10T00:00:00</d:CustomerSince>
 
664
          <d:IsActive m:type="Edm.Boolean">true</d:IsActive>
 
665
          <d:NumOfOrders m:type="Edm.Int64">255</d:NumOfOrders>
 
666
          <d:PartitionKey>mypartitionkey</d:PartitionKey>
 
667
          <d:RowKey>myrowkey1</d:RowKey>
 
668
          <d:Timestamp m:type="Edm.DateTime">0001-01-01T00:00:00</d:Timestamp>
 
669
        </m:properties>
 
670
      </content>
 
671
    </entry>
 
672
    '''
 
673
 
 
674
    # construct the entity body included in <m:properties> and </m:properties>
 
675
    entity_body = '<m:properties xml:space="preserve">{properties}</m:properties>'
 
676
 
 
677
    if isinstance(source, WindowsAzureData):
 
678
        source = vars(source)
 
679
 
 
680
    properties_str = ''
 
681
 
 
682
    # set properties type for types we know if value has no type info.
 
683
    # if value has type info, then set the type to value.type
 
684
    for name, value in source.items():
 
685
        mtype = ''
 
686
        conv = _PYTHON_TO_ENTITY_CONVERSIONS.get(type(value))
 
687
        if conv is None and sys.version_info >= (3,) and value is None:
 
688
            conv = _to_entity_none
 
689
        if conv is None:
 
690
            raise WindowsAzureError(
 
691
                _ERROR_CANNOT_SERIALIZE_VALUE_TO_ENTITY.format(
 
692
                    type(value).__name__))
 
693
 
 
694
        mtype, value = conv(value)
 
695
 
 
696
        # form the property node
 
697
        properties_str += ''.join(['<d:', name])
 
698
        if value is None:
 
699
            properties_str += ' m:null="true" />'
 
700
        else:
 
701
            if mtype:
 
702
                properties_str += ''.join([' m:type="', mtype, '"'])
 
703
            properties_str += ''.join(['>',
 
704
                                      xml_escape(value), '</d:', name, '>'])
 
705
 
 
706
    if sys.version_info < (3,):
 
707
        if isinstance(properties_str, unicode):
 
708
            properties_str = properties_str.encode(encoding='utf-8')
 
709
 
 
710
    # generate the entity_body
 
711
    entity_body = entity_body.format(properties=properties_str)
 
712
    xmlstr = _create_entry(entity_body)
 
713
    return xmlstr
 
714
 
 
715
 
 
716
def _convert_table_to_xml(table_name):
 
717
    '''
 
718
    Create xml to send for a given table name. Since xml format for table is
 
719
    the same as entity and the only difference is that table has only one
 
720
    property 'TableName', so we just call _convert_entity_to_xml.
 
721
 
 
722
    table_name: the name of the table
 
723
    '''
 
724
    return _convert_entity_to_xml({'TableName': table_name})
 
725
 
 
726
 
 
727
def _convert_block_list_to_xml(block_id_list):
 
728
    '''
 
729
    Convert a block list to xml to send.
 
730
 
 
731
    block_id_list:
 
732
        a str list containing the block ids that are used in put_block_list.
 
733
    Only get block from latest blocks.
 
734
    '''
 
735
    if block_id_list is None:
 
736
        return ''
 
737
    xml = '<?xml version="1.0" encoding="utf-8"?><BlockList>'
 
738
    for value in block_id_list:
 
739
        xml += '<Latest>{0}</Latest>'.format(_encode_base64(value))
 
740
 
 
741
    return xml + '</BlockList>'
 
742
 
 
743
 
 
744
def _create_blob_result(response):
 
745
    blob_properties = _parse_response_for_dict(response)
 
746
    return BlobResult(response.body, blob_properties)
 
747
 
 
748
 
 
749
def _convert_response_to_block_list(response):
 
750
    '''
 
751
    Converts xml response to block list class.
 
752
    '''
 
753
    blob_block_list = BlobBlockList()
 
754
 
 
755
    xmldoc = minidom.parseString(response.body)
 
756
    for xml_block in _get_children_from_path(xmldoc,
 
757
                                             'BlockList',
 
758
                                             'CommittedBlocks',
 
759
                                             'Block'):
 
760
        xml_block_id = _decode_base64_to_text(
 
761
            _get_child_nodes(xml_block, 'Name')[0].firstChild.nodeValue)
 
762
        xml_block_size = int(
 
763
            _get_child_nodes(xml_block, 'Size')[0].firstChild.nodeValue)
 
764
        blob_block_list.committed_blocks.append(
 
765
            BlobBlock(xml_block_id, xml_block_size))
 
766
 
 
767
    for xml_block in _get_children_from_path(xmldoc,
 
768
                                             'BlockList',
 
769
                                             'UncommittedBlocks',
 
770
                                             'Block'):
 
771
        xml_block_id = _decode_base64_to_text(
 
772
            _get_child_nodes(xml_block, 'Name')[0].firstChild.nodeValue)
 
773
        xml_block_size = int(
 
774
            _get_child_nodes(xml_block, 'Size')[0].firstChild.nodeValue)
 
775
        blob_block_list.uncommitted_blocks.append(
 
776
            BlobBlock(xml_block_id, xml_block_size))
 
777
 
 
778
    return blob_block_list
 
779
 
 
780
 
 
781
def _remove_prefix(name):
 
782
    colon = name.find(':')
 
783
    if colon != -1:
 
784
        return name[colon + 1:]
 
785
    return name
 
786
 
 
787
 
 
788
def _convert_response_to_entity(response):
 
789
    if response is None:
 
790
        return response
 
791
    return _convert_xml_to_entity(response.body)
 
792
 
 
793
 
 
794
def _convert_xml_to_entity(xmlstr):
 
795
    ''' Convert xml response to entity.
 
796
 
 
797
    The format of entity:
 
798
    <entry xmlns:d="http://schemas.microsoft.com/ado/2007/08/dataservices" xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata" xmlns="http://www.w3.org/2005/Atom">
 
799
      <title />
 
800
      <updated>2008-09-18T23:46:19.3857256Z</updated>
 
801
      <author>
 
802
        <name />
 
803
      </author>
 
804
      <id />
 
805
      <content type="application/xml">
 
806
        <m:properties>
 
807
          <d:Address>Mountain View</d:Address>
 
808
          <d:Age m:type="Edm.Int32">23</d:Age>
 
809
          <d:AmountDue m:type="Edm.Double">200.23</d:AmountDue>
 
810
          <d:BinaryData m:type="Edm.Binary" m:null="true" />
 
811
          <d:CustomerCode m:type="Edm.Guid">c9da6455-213d-42c9-9a79-3e9149a57833</d:CustomerCode>
 
812
          <d:CustomerSince m:type="Edm.DateTime">2008-07-10T00:00:00</d:CustomerSince>
 
813
          <d:IsActive m:type="Edm.Boolean">true</d:IsActive>
 
814
          <d:NumOfOrders m:type="Edm.Int64">255</d:NumOfOrders>
 
815
          <d:PartitionKey>mypartitionkey</d:PartitionKey>
 
816
          <d:RowKey>myrowkey1</d:RowKey>
 
817
          <d:Timestamp m:type="Edm.DateTime">0001-01-01T00:00:00</d:Timestamp>
 
818
        </m:properties>
 
819
      </content>
 
820
    </entry>
 
821
    '''
 
822
    xmldoc = minidom.parseString(xmlstr)
 
823
 
 
824
    xml_properties = None
 
825
    for entry in _get_child_nodes(xmldoc, 'entry'):
 
826
        for content in _get_child_nodes(entry, 'content'):
 
827
            # TODO: Namespace
 
828
            xml_properties = _get_child_nodesNS(
 
829
                content, METADATA_NS, 'properties')
 
830
 
 
831
    if not xml_properties:
 
832
        return None
 
833
 
 
834
    entity = Entity()
 
835
    # extract each property node and get the type from attribute and node value
 
836
    for xml_property in xml_properties[0].childNodes:
 
837
        name = _remove_prefix(xml_property.nodeName)
 
838
        # exclude the Timestamp since it is auto added by azure when
 
839
        # inserting entity. We don't want this to mix with real properties
 
840
        if name in ['Timestamp']:
 
841
            continue
 
842
 
 
843
        if xml_property.firstChild:
 
844
            value = xml_property.firstChild.nodeValue
 
845
        else:
 
846
            value = ''
 
847
 
 
848
        isnull = xml_property.getAttributeNS(METADATA_NS, 'null')
 
849
        mtype = xml_property.getAttributeNS(METADATA_NS, 'type')
 
850
 
 
851
        # if not isnull and no type info, then it is a string and we just
 
852
        # need the str type to hold the property.
 
853
        if not isnull and not mtype:
 
854
            _set_entity_attr(entity, name, value)
 
855
        elif isnull == 'true':
 
856
            if mtype:
 
857
                property = EntityProperty(mtype, None)
 
858
            else:
 
859
                property = EntityProperty('Edm.String', None)
 
860
        else:  # need an object to hold the property
 
861
            conv = _ENTITY_TO_PYTHON_CONVERSIONS.get(mtype)
 
862
            if conv is not None:
 
863
                property = conv(value)
 
864
            else:
 
865
                property = EntityProperty(mtype, value)
 
866
            _set_entity_attr(entity, name, property)
 
867
 
 
868
        # extract id, updated and name value from feed entry and set them of
 
869
        # rule.
 
870
    for name, value in _get_entry_properties(xmlstr, True).items():
 
871
        if name in ['etag']:
 
872
            _set_entity_attr(entity, name, value)
 
873
 
 
874
    return entity
 
875
 
 
876
 
 
877
def _set_entity_attr(entity, name, value):
 
878
    try:
 
879
        setattr(entity, name, value)
 
880
    except UnicodeEncodeError:
 
881
        # Python 2 doesn't support unicode attribute names, so we'll
 
882
        # add them and access them directly through the dictionary
 
883
        entity.__dict__[name] = value
 
884
 
 
885
 
 
886
def _convert_xml_to_table(xmlstr):
 
887
    ''' Converts the xml response to table class.
 
888
    Simply call convert_xml_to_entity and extract the table name, and add
 
889
    updated and author info
 
890
    '''
 
891
    table = Table()
 
892
    entity = _convert_xml_to_entity(xmlstr)
 
893
    setattr(table, 'name', entity.TableName)
 
894
    for name, value in _get_entry_properties(xmlstr, False).items():
 
895
        setattr(table, name, value)
 
896
    return table
 
897
 
 
898
 
 
899
def _storage_error_handler(http_error):
 
900
    ''' Simple error handler for storage service. '''
 
901
    return _general_error_handler(http_error)
 
902
 
 
903
# make these available just from storage.
 
904
from azure.storage.blobservice import BlobService
 
905
from azure.storage.queueservice import QueueService
 
906
from azure.storage.tableservice import TableService
 
907
from azure.storage.cloudstorageaccount import CloudStorageAccount
 
908
from azure.storage.sharedaccesssignature import (
 
909
    SharedAccessSignature,
 
910
    SharedAccessPolicy,
 
911
    Permission,
 
912
    WebResource,
 
913
    )