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#--------------------------------------------------------------------------
15from contextlib import contextmanager
16from azure.common import (
17    AzureHttpError,
18)
19from .._common_conversion import (
20    _int_to_str,
21    _to_str,
22)
23from .._error import (
24    _dont_fail_not_exist,
25    _dont_fail_on_exist,
26    _validate_not_none,
27    _ERROR_STORAGE_MISSING_INFO,
28    _validate_access_policies,
29)
30from .._serialization import (
31    _get_request_body,
32    _update_request,
33    _convert_signed_identifiers_to_xml,
34    _convert_service_properties_to_xml,
35)
36from .._http import HTTPRequest
37from ..models import (
38    Services,
39    ListGenerator,
40    _OperationContext,
41)
42from .models import TablePayloadFormat
43from .._auth import (
44    _StorageSASAuthentication,
45    _StorageTableSharedKeyAuthentication,
46)
47from .._connection import _ServiceParameters
48from .._deserialization import (
49    _convert_xml_to_service_properties,
50    _convert_xml_to_signed_identifiers,
51    _convert_xml_to_service_stats,
52)
53from ._serialization import (
54    _convert_table_to_json,
55    _convert_batch_to_json,
56    _update_storage_table_header,
57    _get_entity_path,
58    _DEFAULT_ACCEPT_HEADER,
59    _DEFAULT_CONTENT_TYPE_HEADER,
60    _DEFAULT_PREFER_HEADER,
61)
62from ._deserialization import (
63    _convert_json_response_to_entity,
64    _convert_json_response_to_tables,
65    _convert_json_response_to_entities,
66    _parse_batch_response,
67    _extract_etag,
68)
69from .._constants import (
70    SERVICE_HOST_BASE,
71    DEFAULT_PROTOCOL,
72)
73from ._request import (
74    _get_entity,
75    _insert_entity,
76    _update_entity,
77    _merge_entity,
78    _delete_entity,
79    _insert_or_replace_entity,
80    _insert_or_merge_entity,
81)
82from ..sharedaccesssignature import (
83    SharedAccessSignature,
84)
85from ..storageclient import StorageClient
86from .tablebatch import TableBatch
87
88class TableService(StorageClient):
89
90    '''
91    This is the main class managing Azure Table resources.
92
93    The Azure Table service offers structured storage in the form of tables. Tables
94    store data as collections of entities. Entities are similar to rows. An entity
95    has a primary key and a set of properties. A property is a name, typed-value pair,
96    similar to a column. The Table service does not enforce any schema for tables,
97    so two entities in the same table may have different sets of properties. Developers
98    may choose to enforce a schema on the client side. A table may contain any number
99    of entities.
100
101    :ivar object key_encryption_key:
102        The key-encryption-key optionally provided by the user. If provided, will be used to
103        encrypt/decrypt in supported methods.
104        For methods requiring decryption, either the key_encryption_key OR the resolver must be provided.
105        If both are provided, the resolver will take precedence.
106        Must implement the following methods for APIs requiring encryption:
107        wrap_key(key)--wraps the specified key (bytes) using an algorithm of the user's choice. Returns the encrypted key as bytes.
108        get_key_wrap_algorithm()--returns the algorithm used to wrap the specified symmetric key.
109        get_kid()--returns a string key id for this key-encryption-key.
110        Must implement the following methods for APIs requiring decryption:
111        unwrap_key(key, algorithm)--returns the unwrapped form of the specified symmetric key using the string-specified algorithm.
112        get_kid()--returns a string key id for this key-encryption-key.
113    :ivar function key_resolver_function(kid):
114        A function to resolve keys optionally provided by the user. If provided, will be used to decrypt in supported methods.
115        For methods requiring decryption, either the key_encryption_key OR
116        the resolver must be provided. If both are provided, the resolver will take precedence.
117        It uses the kid string to return a key-encryption-key implementing the interface defined above.
118    :ivar function(partition_key, row_key, property_name) encryption_resolver_functions:
119        A function that takes in an entity's partition key, row key, and property name and returns
120        a boolean that indicates whether that property should be encrypted.
121    :ivar bool require_encryption:
122        A flag that may be set to ensure that all messages successfully uploaded to the queue and all those downloaded and
123        successfully read from the queue are/were encrypted while on the server. If this flag is set, all required
124        parameters for encryption/decryption must be provided. See the above comments on the key_encryption_key and resolver.
125    '''
126
127    def __init__(self, account_name=None, account_key=None, sas_token=None,
128                 is_emulated=False, protocol=DEFAULT_PROTOCOL, endpoint_suffix=SERVICE_HOST_BASE,
129                 request_session=None, connection_string=None, socket_timeout=None):
130        '''
131        :param str account_name:
132            The storage account name. This is used to authenticate requests
133            signed with an account key and to construct the storage endpoint. It
134            is required unless a connection string is given.
135        :param str account_key:
136            The storage account key. This is used for shared key authentication.
137        :param str sas_token:
138             A shared access signature token to use to authenticate requests
139             instead of the account key. If account key and sas token are both
140             specified, account key will be used to sign.
141        :param bool is_emulated:
142            Whether to use the emulator. Defaults to False. If specified, will
143            override all other parameters besides connection string and request
144            session.
145        :param str protocol:
146            The protocol to use for requests. Defaults to https.
147        :param str endpoint_suffix:
148            The host base component of the url, minus the account name. Defaults
149            to Azure (core.windows.net). Override this to use the China cloud
150            (core.chinacloudapi.cn).
151        :param requests.Session request_session:
152            The session object to use for http requests.
153        :param str connection_string:
154            If specified, this will override all other parameters besides
155            request session. See
156            http://azure.microsoft.com/en-us/documentation/articles/storage-configure-connection-string/
157            for the connection string format.
158        :param int socket_timeout:
159            If specified, this will override the default socket timeout. The timeout specified is in seconds.
160            See DEFAULT_SOCKET_TIMEOUT in _constants.py for the default value.
161        '''
162        service_params = _ServiceParameters.get_service_parameters(
163            'table',
164            account_name=account_name,
165            account_key=account_key,
166            sas_token=sas_token,
167            is_emulated=is_emulated,
168            protocol=protocol,
169            endpoint_suffix=endpoint_suffix,
170            request_session=request_session,
171            connection_string=connection_string,
172            socket_timeout=socket_timeout)
173
174        super(TableService, self).__init__(service_params)
175
176        if self.account_key:
177            self.authentication = _StorageTableSharedKeyAuthentication(
178                self.account_name,
179                self.account_key,
180            )
181        elif self.sas_token:
182            self.authentication = _StorageSASAuthentication(self.sas_token)
183        else:
184            raise ValueError(_ERROR_STORAGE_MISSING_INFO)
185
186        self.require_encryption = False
187        self.key_encryption_key = None
188        self.key_resolver_function = None
189        self.encryption_resolver_function = None
190
191    def generate_account_shared_access_signature(self, resource_types, permission,
192                                        expiry, start=None, ip=None, protocol=None):
193        '''
194        Generates a shared access signature for the table service.
195        Use the returned signature with the sas_token parameter of TableService.
196
197        :param ResourceTypes resource_types:
198            Specifies the resource types that are accessible with the account SAS.
199        :param AccountPermissions permission:
200            The permissions associated with the shared access signature. The
201            user is restricted to operations allowed by the permissions.
202            Required unless an id is given referencing a stored access policy
203            which contains this field. This field must be omitted if it has been
204            specified in an associated stored access policy.
205        :param expiry:
206            The time at which the shared access signature becomes invalid.
207            Required unless an id is given referencing a stored access policy
208            which contains this field. This field must be omitted if it has
209            been specified in an associated stored access policy. Azure will always
210            convert values to UTC. If a date is passed in without timezone info, it
211            is assumed to be UTC.
212        :type expiry: date or str
213        :param start:
214            The time at which the shared access signature becomes valid. If
215            omitted, start time for this call is assumed to be the time when the
216            storage service receives the request. Azure will always convert values
217            to UTC. If a date is passed in without timezone info, it is assumed to
218            be UTC.
219        :type start: date or str
220        :param str ip:
221            Specifies an IP address or a range of IP addresses from which to accept requests.
222            If the IP address from which the request originates does not match the IP address
223            or address range specified on the SAS token, the request is not authenticated.
224            For example, specifying sip=168.1.5.65 or sip=168.1.5.60-168.1.5.70 on the SAS
225            restricts the request to those IP addresses.
226        :param str protocol:
227            Specifies the protocol permitted for a request made. The default value
228            is https,http. See :class:`~azure.storage.models.Protocol` for possible values.
229        :return: A Shared Access Signature (sas) token.
230        :rtype: str
231        '''
232        _validate_not_none('self.account_name', self.account_name)
233        _validate_not_none('self.account_key', self.account_key)
234
235        sas = SharedAccessSignature(self.account_name, self.account_key)
236        return sas.generate_account(Services.TABLE, resource_types, permission,
237                                    expiry, start=start, ip=ip, protocol=protocol)
238
239
240    def generate_table_shared_access_signature(self, table_name, permission=None,
241                                        expiry=None, start=None, id=None,
242                                        ip=None, protocol=None,
243                                        start_pk=None, start_rk=None,
244                                        end_pk=None, end_rk=None):
245        '''
246        Generates a shared access signature for the table.
247        Use the returned signature with the sas_token parameter of TableService.
248
249        :param str table_name:
250            The name of the table to create a SAS token for.
251        :param TablePermissions permission:
252            The permissions associated with the shared access signature. The
253            user is restricted to operations allowed by the permissions.
254            Required unless an id is given referencing a stored access policy
255            which contains this field. This field must be omitted if it has been
256            specified in an associated stored access policy.
257        :param expiry:
258            The time at which the shared access signature becomes invalid.
259            Required unless an id is given referencing a stored access policy
260            which contains this field. This field must be omitted if it has
261            been specified in an associated stored access policy. Azure will always
262            convert values to UTC. If a date is passed in without timezone info, it
263            is assumed to be UTC.
264        :type expiry: date or str
265        :param start:
266            The time at which the shared access signature becomes valid. If
267            omitted, start time for this call is assumed to be the time when the
268            storage service receives the request. Azure will always convert values
269            to UTC. If a date is passed in without timezone info, it is assumed to
270            be UTC.
271        :type start: date or str
272        :param str id:
273            A unique value up to 64 characters in length that correlates to a
274            stored access policy. To create a stored access policy, use :func:`~set_table_acl`.
275        :param str ip:
276            Specifies an IP address or a range of IP addresses from which to accept requests.
277            If the IP address from which the request originates does not match the IP address
278            or address range specified on the SAS token, the request is not authenticated.
279            For example, specifying sip='168.1.5.65' or sip='168.1.5.60-168.1.5.70' on the SAS
280            restricts the request to those IP addresses.
281        :param str protocol:
282            Specifies the protocol permitted for a request made. The default value
283            is https,http. See :class:`~azure.storage.models.Protocol` for possible values.
284        :param str start_pk:
285            The minimum partition key accessible with this shared access
286            signature. startpk must accompany startrk. Key values are inclusive.
287            If omitted, there is no lower bound on the table entities that can
288            be accessed.
289        :param str start_rk:
290            The minimum row key accessible with this shared access signature.
291            startpk must accompany startrk. Key values are inclusive. If
292            omitted, there is no lower bound on the table entities that can be
293            accessed.
294        :param str end_pk:
295            The maximum partition key accessible with this shared access
296            signature. endpk must accompany endrk. Key values are inclusive. If
297            omitted, there is no upper bound on the table entities that can be
298            accessed.
299        :param str end_rk:
300            The maximum row key accessible with this shared access signature.
301            endpk must accompany endrk. Key values are inclusive. If omitted,
302            there is no upper bound on the table entities that can be accessed.
303        :return: A Shared Access Signature (sas) token.
304        :rtype: str
305        '''
306        _validate_not_none('table_name', table_name)
307        _validate_not_none('self.account_name', self.account_name)
308        _validate_not_none('self.account_key', self.account_key)
309
310        sas = SharedAccessSignature(self.account_name, self.account_key)
311        return sas.generate_table(
312            table_name,
313            permission=permission,
314            expiry=expiry,
315            start=start,
316            id=id,
317            ip=ip,
318            protocol=protocol,
319            start_pk=start_pk,
320            start_rk=start_rk,
321            end_pk=end_pk,
322            end_rk=end_rk,
323        )
324
325    def get_table_service_stats(self, timeout=None):
326        '''
327        Retrieves statistics related to replication for the Table service. It is
328        only available when read-access geo-redundant replication is enabled for
329        the storage account.
330
331        With geo-redundant replication, Azure Storage maintains your data durable
332        in two locations. In both locations, Azure Storage constantly maintains
333        multiple healthy replicas of your data. The location where you read,
334        create, update, or delete data is the primary storage account location.
335        The primary location exists in the region you choose at the time you
336        create an account via the Azure Management Azure classic portal, for
337        example, North Central US. The location to which your data is replicated
338        is the secondary location. The secondary location is automatically
339        determined based on the location of the primary; it is in a second data
340        center that resides in the same region as the primary location. Read-only
341        access is available from the secondary location, if read-access geo-redundant
342        replication is enabled for your storage account.
343
344        :param int timeout:
345            The timeout parameter is expressed in seconds.
346        :return: The table service stats.
347        :rtype: :class:`~azure.storage.models.ServiceStats`
348        '''
349        request = HTTPRequest()
350        request.method = 'GET'
351        request.host_locations = self._get_host_locations(primary=False, secondary=True)
352        request.path = '/'
353        request.query = {
354            'restype': 'service',
355            'comp': 'stats',
356            'timeout': _int_to_str(timeout),
357        }
358
359        return self._perform_request(request, _convert_xml_to_service_stats)
360
361    def get_table_service_properties(self, timeout=None):
362        '''
363        Gets the properties of a storage account's Table service, including
364        logging, analytics and CORS rules.
365
366        :param int timeout:
367            The server timeout, expressed in seconds.
368        :return: The table service properties.
369        :rtype: :class:`~azure.storage.models.ServiceProperties`
370        '''
371        request = HTTPRequest()
372        request.method = 'GET'
373        request.host_locations = self._get_host_locations(secondary=True)
374        request.path = '/'
375        request.query = {
376            'restype': 'service',
377            'comp': 'properties',
378            'timeout': _int_to_str(timeout),
379        }
380
381        return self._perform_request(request, _convert_xml_to_service_properties)
382
383    def set_table_service_properties(self, logging=None, hour_metrics=None,
384                                    minute_metrics=None, cors=None, timeout=None):
385        '''
386        Sets the properties of a storage account's Table service, including
387        Azure Storage Analytics. If an element (ex Logging) is left as None, the
388        existing settings on the service for that functionality are preserved.
389        For more information on Azure Storage Analytics, see
390        https://msdn.microsoft.com/en-us/library/azure/hh343270.aspx.
391
392        :param Logging logging:
393            The logging settings provide request logs.
394        :param Metrics hour_metrics:
395            The hour metrics settings provide a summary of request
396            statistics grouped by API in hourly aggregates for tables.
397        :param Metrics minute_metrics:
398            The minute metrics settings provide request statistics
399            for each minute for tables.
400        :param cors:
401            You can include up to five CorsRule elements in the
402            list. If an empty list is specified, all CORS rules will be deleted,
403            and CORS will be disabled for the service. For detailed information
404            about CORS rules and evaluation logic, see
405            https://msdn.microsoft.com/en-us/library/azure/dn535601.aspx.
406        :type cors: list of :class:`~azure.storage.models.CorsRule`
407        :param int timeout:
408            The server timeout, expressed in seconds.
409        '''
410        request = HTTPRequest()
411        request.method = 'PUT'
412        request.host_locations = self._get_host_locations()
413        request.path = '/'
414        request.query = {
415            'restype': 'service',
416            'comp': 'properties',
417            'timeout': _int_to_str(timeout),
418        }
419        request.body = _get_request_body(
420            _convert_service_properties_to_xml(logging, hour_metrics, minute_metrics, cors))
421
422        self._perform_request(request)
423
424    def list_tables(self, num_results=None, marker=None, timeout=None):
425        '''
426        Returns a generator to list the tables. The generator will lazily follow
427        the continuation tokens returned by the service and stop when all tables
428        have been returned or num_results is reached.
429
430        If num_results is specified and the account has more than that number of
431        tables, the generator will have a populated next_marker field once it
432        finishes. This marker can be used to create a new generator if more
433        results are desired.
434
435        :param int num_results:
436            The maximum number of tables to return.
437        :param marker:
438            An opaque continuation object. This value can be retrieved from the
439            next_marker field of a previous generator object if num_results was
440            specified and that generator has finished enumerating results. If
441            specified, this generator will begin returning results from the point
442            where the previous generator stopped.
443        :type marker: obj
444        :param int timeout:
445            The server timeout, expressed in seconds. This function may make multiple
446            calls to the service in which case the timeout value specified will be
447            applied to each individual call.
448        :return: A generator which produces :class:`~azure.storage.models.table.Table` objects.
449        :rtype: :class:`~azure.storage.models.ListGenerator`:
450        '''
451        operation_context = _OperationContext(location_lock=True)
452        kwargs = {'max_results': num_results, 'marker': marker, 'timeout': timeout,
453                  '_context': operation_context}
454        resp = self._list_tables(**kwargs)
455
456        return ListGenerator(resp, self._list_tables, (), kwargs)
457
458    def _list_tables(self, max_results=None, marker=None, timeout=None, _context=None):
459        '''
460        Returns a list of tables under the specified account. Makes a single list
461        request to the service. Used internally by the list_tables method.
462
463        :param int max_results:
464            The maximum number of tables to return. A single list request may
465            return up to 1000 tables and potentially a continuation token which
466            should be followed to get additional resutls.
467        :param marker:
468            A dictionary which identifies the portion of the query to be
469            returned with the next query operation. The operation returns a
470            next_marker element within the response body if the list returned
471            was not complete. This value may then be used as a query parameter
472            in a subsequent call to request the next portion of the list of
473            tables. The marker value is opaque to the client.
474        :type marker: obj
475        :param int timeout:
476            The server timeout, expressed in seconds.
477        :return: A list of tables, potentially with a next_marker property.
478        :rtype: list of :class:`~azure.storage.models.table.Table`:
479        '''
480        request = HTTPRequest()
481        request.method = 'GET'
482        request.host_locations = self._get_host_locations(secondary=True)
483        request.path = '/Tables'
484        request.headers = {'Accept': TablePayloadFormat.JSON_NO_METADATA}
485        request.query = {
486            '$top': _int_to_str(max_results),
487            'NextTableName': _to_str(marker),
488            'timeout': _int_to_str(timeout),
489        }
490
491        return self._perform_request(request, _convert_json_response_to_tables,
492                                     operation_context=_context)
493
494    def create_table(self, table_name, fail_on_exist=False, timeout=None):
495        '''
496        Creates a new table in the storage account.
497
498        :param str table_name:
499            The name of the table to create. The table name may contain only
500            alphanumeric characters and cannot begin with a numeric character.
501            It is case-insensitive and must be from 3 to 63 characters long.
502        :param bool fail_on_exist:
503            Specifies whether to throw an exception if the table already exists.
504        :param int timeout:
505            The server timeout, expressed in seconds.
506        :return:
507            A boolean indicating whether the table was created. If fail_on_exist
508            was set to True, this will throw instead of returning false.
509        :rtype: bool
510        '''
511        _validate_not_none('table', table_name)
512        request = HTTPRequest()
513        request.method = 'POST'
514        request.host_locations = self._get_host_locations()
515        request.path = '/Tables'
516        request.query = {'timeout': _int_to_str(timeout)}
517        request.headers = {
518            _DEFAULT_CONTENT_TYPE_HEADER[0]: _DEFAULT_CONTENT_TYPE_HEADER[1],
519            _DEFAULT_PREFER_HEADER[0]: _DEFAULT_PREFER_HEADER[1],
520            _DEFAULT_ACCEPT_HEADER[0]: _DEFAULT_ACCEPT_HEADER[1]
521        }
522        request.body = _get_request_body(_convert_table_to_json(table_name))
523
524        if not fail_on_exist:
525            try:
526                self._perform_request(request)
527                return True
528            except AzureHttpError as ex:
529                _dont_fail_on_exist(ex)
530                return False
531        else:
532            self._perform_request(request)
533            return True
534
535    def exists(self, table_name, timeout=None):
536        '''
537        Returns a boolean indicating whether the table exists.
538
539        :param str table_name:
540            The name of table to check for existence.
541        :param int timeout:
542            The server timeout, expressed in seconds.
543        :return: A boolean indicating whether the table exists.
544        :rtype: bool
545        '''
546        _validate_not_none('table_name', table_name)
547        request = HTTPRequest()
548        request.method = 'GET'
549        request.host_locations = self._get_host_locations(secondary=True)
550        request.path = '/Tables' + "('" + table_name + "')"
551        request.headers = {'Accept': TablePayloadFormat.JSON_NO_METADATA}
552        request.query = {'timeout': _int_to_str(timeout)}
553
554        try:
555            self._perform_request(request)
556            return True
557        except AzureHttpError as ex:
558            _dont_fail_not_exist(ex)
559            return False
560
561    def delete_table(self, table_name, fail_not_exist=False, timeout=None):
562        '''
563        Deletes the specified table and any data it contains.
564
565        When a table is successfully deleted, it is immediately marked for deletion
566        and is no longer accessible to clients. The table is later removed from
567        the Table service during garbage collection.
568
569        Note that deleting a table is likely to take at least 40 seconds to complete.
570        If an operation is attempted against the table while it was being deleted,
571        an :class:`AzureConflictHttpError` will be thrown.
572
573        :param str table_name:
574            The name of the table to delete.
575        :param bool fail_not_exist:
576            Specifies whether to throw an exception if the table doesn't exist.
577        :param int timeout:
578            The server timeout, expressed in seconds.
579        :return:
580            A boolean indicating whether the table was deleted. If fail_not_exist
581            was set to True, this will throw instead of returning false.
582        :rtype: bool
583        '''
584        _validate_not_none('table_name', table_name)
585        request = HTTPRequest()
586        request.method = 'DELETE'
587        request.host_locations = self._get_host_locations()
588        request.path = '/Tables(\'' + _to_str(table_name) + '\')'
589        request.query = {'timeout': _int_to_str(timeout)}
590        request.headers = {_DEFAULT_ACCEPT_HEADER[0]: _DEFAULT_ACCEPT_HEADER[1]}
591
592        if not fail_not_exist:
593            try:
594                self._perform_request(request)
595                return True
596            except AzureHttpError as ex:
597                _dont_fail_not_exist(ex)
598                return False
599        else:
600            self._perform_request(request)
601            return True
602
603    def get_table_acl(self, table_name, timeout=None):
604        '''
605        Returns details about any stored access policies specified on the
606        table that may be used with Shared Access Signatures.
607
608        :param str table_name:
609            The name of an existing table.
610        :param int timeout:
611            The server timeout, expressed in seconds.
612        :return: A dictionary of access policies associated with the table.
613        :rtype: dict of str to :class:`~azure.storage.models.AccessPolicy`:
614        '''
615        _validate_not_none('table_name', table_name)
616        request = HTTPRequest()
617        request.method = 'GET'
618        request.host_locations = self._get_host_locations(secondary=True)
619        request.path = '/' + _to_str(table_name)
620        request.query = {
621            'comp': 'acl',
622            'timeout': _int_to_str(timeout),
623        }
624
625        return self._perform_request(request, _convert_xml_to_signed_identifiers)
626
627    def set_table_acl(self, table_name, signed_identifiers=None, timeout=None):
628        '''
629        Sets stored access policies for the table that may be used with Shared
630        Access Signatures.
631
632        When you set permissions for a table, the existing permissions are replaced.
633        To update the table's permissions, call :func:`~get_table_acl` to fetch
634        all access policies associated with the table, modify the access policy
635        that you wish to change, and then call this function with the complete
636        set of data to perform the update.
637
638        When you establish a stored access policy on a table, it may take up to
639        30 seconds to take effect. During this interval, a shared access signature
640        that is associated with the stored access policy will throw an
641        :class:`AzureHttpError` until the access policy becomes active.
642
643        :param str table_name:
644            The name of an existing table.
645        :param signed_identifiers:
646            A dictionary of access policies to associate with the table. The
647            dictionary may contain up to 5 elements. An empty dictionary
648            will clear the access policies set on the service.
649        :type signed_identifiers: dict of str to :class:`~azure.storage.models.AccessPolicy`
650        :param int timeout:
651            The server timeout, expressed in seconds.
652        '''
653        _validate_not_none('table_name', table_name)
654        _validate_access_policies(signed_identifiers)
655        request = HTTPRequest()
656        request.method = 'PUT'
657        request.host_locations = self._get_host_locations()
658        request.path = '/' + _to_str(table_name)
659        request.query = {
660            'comp': 'acl',
661            'timeout': _int_to_str(timeout),
662        }
663        request.body = _get_request_body(
664            _convert_signed_identifiers_to_xml(signed_identifiers))
665
666        self._perform_request(request)
667
668    def query_entities(self, table_name, filter=None, select=None, num_results=None,
669                       marker=None, accept=TablePayloadFormat.JSON_MINIMAL_METADATA,
670                       property_resolver=None, timeout=None):
671        '''
672        Returns a generator to list the entities in the table specified. The
673        generator will lazily follow the continuation tokens returned by the
674        service and stop when all entities have been returned or max_results is
675        reached.
676
677        If max_results is specified and the account has more than that number of
678        entities, the generator will have a populated next_marker field once it
679        finishes. This marker can be used to create a new generator if more
680        results are desired.
681
682        :param str table_name:
683            The name of the table to query.
684        :param str filter:
685            Returns only entities that satisfy the specified filter. Note that
686            no more than 15 discrete comparisons are permitted within a $filter
687            string. See http://msdn.microsoft.com/en-us/library/windowsazure/dd894031.aspx
688            for more information on constructing filters.
689        :param str select:
690            Returns only the desired properties of an entity from the set.
691        :param int num_results:
692            The maximum number of entities to return.
693        :param marker:
694            An opaque continuation object. This value can be retrieved from the
695            next_marker field of a previous generator object if max_results was
696            specified and that generator has finished enumerating results. If
697            specified, this generator will begin returning results from the point
698            where the previous generator stopped.
699        :type marker: obj
700        :param str accept:
701            Specifies the accepted content type of the response payload. See
702            :class:`~azure.storage.table.models.TablePayloadFormat` for possible
703            values.
704        :param property_resolver:
705            A function which given the partition key, row key, property name,
706            property value, and the property EdmType if returned by the service,
707            returns the EdmType of the property. Generally used if accept is set
708            to JSON_NO_METADATA.
709        :type property_resolver: callback function in format of func(pk, rk, prop_name, prop_value, service_edm_type)
710        :param int timeout:
711            The server timeout, expressed in seconds. This function may make multiple
712            calls to the service in which case the timeout value specified will be
713            applied to each individual call.
714        :return: A generator which produces :class:`~azure.storage.table.models.Entity` objects.
715        :rtype: :class:`~azure.storage.models.ListGenerator`
716        '''
717
718        operation_context = _OperationContext(location_lock=True)
719        if self.key_encryption_key is not None or self.key_resolver_function is not None:
720            # If query already requests all properties, no need to add the metadata columns
721            if select is not None and select != '*':
722                select += ',_ClientEncryptionMetadata1,_ClientEncryptionMetadata2'
723
724        args = (table_name,)
725        kwargs = {'filter': filter, 'select': select, 'max_results': num_results, 'marker': marker,
726                  'accept': accept, 'property_resolver': property_resolver, 'timeout': timeout,
727                  '_context': operation_context}
728        resp = self._query_entities(*args, **kwargs)
729
730        return ListGenerator(resp, self._query_entities, args, kwargs)
731
732    def _query_entities(self, table_name, filter=None, select=None, max_results=None,
733                       marker=None, accept=TablePayloadFormat.JSON_MINIMAL_METADATA,
734                       property_resolver=None, timeout=None, _context=None):
735        '''
736        Returns a list of entities under the specified table. Makes a single list
737        request to the service. Used internally by the query_entities method.
738
739        :param str table_name:
740            The name of the table to query.
741        :param str filter:
742            Returns only entities that satisfy the specified filter. Note that
743            no more than 15 discrete comparisons are permitted within a $filter
744            string. See http://msdn.microsoft.com/en-us/library/windowsazure/dd894031.aspx
745            for more information on constructing filters.
746        :param str select:
747            Returns only the desired properties of an entity from the set.
748        :param int top:
749            The maximum number of entities to return.
750        :param marker:
751            A dictionary which identifies the portion of the query to be
752            returned with the next query operation. The operation returns a
753            next_marker element within the response body if the list returned
754            was not complete. This value may then be used as a query parameter
755            in a subsequent call to request the next portion of the list of
756            table. The marker value is opaque to the client.
757        :type marker: obj
758        :param str accept:
759            Specifies the accepted content type of the response payload. See
760            :class:`~azure.storage.table.models.TablePayloadFormat` for possible
761            values.
762        :param property_resolver:
763            A function which given the partition key, row key, property name,
764            property value, and the property EdmType if returned by the service,
765            returns the EdmType of the property. Generally used if accept is set
766            to JSON_NO_METADATA.
767        :type property_resolver: callback function in format of func(pk, rk, prop_name, prop_value, service_edm_type)
768        :param int timeout:
769            The server timeout, expressed in seconds.
770        :return: A list of entities, potentially with a next_marker property.
771        :rtype: list of :class:`~azure.storage.table.models.Entity`
772        '''
773        _validate_not_none('table_name', table_name)
774        _validate_not_none('accept', accept)
775        next_partition_key = None if marker is None else marker.get('nextpartitionkey')
776        next_row_key = None if marker is None else marker.get('nextrowkey')
777
778        request = HTTPRequest()
779        request.method = 'GET'
780        request.host_locations = self._get_host_locations(secondary=True)
781        request.path = '/' + _to_str(table_name) + '()'
782        request.headers = {'Accept': _to_str(accept)}
783        request.query = {
784            '$filter': _to_str(filter),
785            '$select': _to_str(select),
786            '$top': _int_to_str(max_results),
787            'NextPartitionKey': _to_str(next_partition_key),
788            'NextRowKey': _to_str(next_row_key),
789            'timeout': _int_to_str(timeout),
790        }
791
792        return self._perform_request(request, _convert_json_response_to_entities,
793                                     [property_resolver, self.require_encryption,
794                                      self.key_encryption_key, self.key_resolver_function],
795                                      operation_context=_context)
796
797    def commit_batch(self, table_name, batch, timeout=None):
798        '''
799        Commits a :class:`~azure.storage.table.TableBatch` request.
800
801        :param str table_name:
802            The name of the table to commit the batch to.
803        :param TableBatch batch:
804            The batch to commit.
805        :param int timeout:
806            The server timeout, expressed in seconds.
807        :return: A list of the batch responses corresponding to the requests in the batch.
808        :rtype: list of response objects
809        '''
810        _validate_not_none('table_name', table_name)
811
812        # Construct the batch request
813        request = HTTPRequest()
814        request.method = 'POST'
815        request.host_locations = self._get_host_locations()
816        request.path = '/' + '$batch'
817        request.query = {'timeout': _int_to_str(timeout)}
818
819        # Update the batch operation requests with table and client specific info
820        for row_key, batch_request in batch._requests:
821            if batch_request.method == 'POST':
822                batch_request.path = '/' + _to_str(table_name)
823            else:
824                batch_request.path = _get_entity_path(table_name, batch._partition_key, row_key)
825            _update_request(batch_request)
826
827        # Construct the batch body
828        request.body, boundary = _convert_batch_to_json(batch._requests)
829        request.headers = {'Content-Type': boundary}
830
831        # Perform the batch request and return the response
832        return self._perform_request(request, _parse_batch_response)
833
834    @contextmanager
835    def batch(self, table_name, timeout=None):
836        '''
837        Creates a batch object which can be used as a context manager. Commits the batch on exit.
838
839        :param str table_name:
840            The name of the table to commit the batch to.
841        :param int timeout:
842            The server timeout, expressed in seconds.
843        '''
844        batch = TableBatch(self.require_encryption, self.key_encryption_key, self.encryption_resolver_function)
845        yield batch
846        self.commit_batch(table_name, batch, timeout=timeout)
847
848    def get_entity(self, table_name, partition_key, row_key, select=None,
849                   accept=TablePayloadFormat.JSON_MINIMAL_METADATA,
850                   property_resolver=None, timeout=None):
851        '''
852        Get an entity from the specified table. Throws if the entity does not exist.
853
854        :param str table_name:
855            The name of the table to get the entity from.
856        :param str partition_key:
857            The PartitionKey of the entity.
858        :param str row_key:
859            The RowKey of the entity.
860        :param str select:
861            Returns only the desired properties of an entity from the set.
862        :param str accept:
863            Specifies the accepted content type of the response payload. See
864            :class:`~azure.storage.table.models.TablePayloadFormat` for possible
865            values.
866        :param property_resolver:
867            A function which given the partition key, row key, property name,
868            property value, and the property EdmType if returned by the service,
869            returns the EdmType of the property. Generally used if accept is set
870            to JSON_NO_METADATA.
871        :type property_resolver: callback function in format of func(pk, rk, prop_name, prop_value, service_edm_type)
872        :param int timeout:
873            The server timeout, expressed in seconds.
874        :return: The retrieved entity.
875        :rtype: :class:`~azure.storage.table.models.Entity`
876        '''
877        _validate_not_none('table_name', table_name)
878        request = _get_entity(partition_key, row_key, select, accept)
879        request.host_locations = self._get_host_locations(secondary=True)
880        request.path = _get_entity_path(table_name, partition_key, row_key)
881        request.query['timeout'] = _int_to_str(timeout)
882
883        return self._perform_request(request, _convert_json_response_to_entity,
884                                     [property_resolver, self.require_encryption,
885                                      self.key_encryption_key, self.key_resolver_function])
886
887    def insert_entity(self, table_name, entity, timeout=None):
888        '''
889        Inserts a new entity into the table. Throws if an entity with the same
890        PartitionKey and RowKey already exists.
891
892        When inserting an entity into a table, you must specify values for the
893        PartitionKey and RowKey system properties. Together, these properties
894        form the primary key and must be unique within the table. Both the
895        PartitionKey and RowKey values must be string values; each key value may
896        be up to 64 KB in size. If you are using an integer value for the key
897        value, you should convert the integer to a fixed-width string, because
898        they are canonically sorted. For example, you should convert the value
899        1 to 0000001 to ensure proper sorting.
900
901        :param str table_name:
902            The name of the table to insert the entity into.
903        :param entity:
904            The entity to insert. Could be a dict or an entity object.
905            Must contain a PartitionKey and a RowKey.
906        :type entity: a dict or :class:`~azure.storage.table.models.Entity`
907        :param int timeout:
908            The server timeout, expressed in seconds.
909        :return: The etag of the inserted entity.
910        :rtype: str
911        '''
912        _validate_not_none('table_name', table_name)
913
914        request = _insert_entity(entity, self.require_encryption, self.key_encryption_key,
915                                 self.encryption_resolver_function)
916        request.host_locations = self._get_host_locations()
917        request.path = '/' + _to_str(table_name)
918        request.query['timeout'] = _int_to_str(timeout)
919
920        return self._perform_request(request, _extract_etag)
921
922    def update_entity(self, table_name, entity, if_match='*', timeout=None):
923        '''
924        Updates an existing entity in a table. Throws if the entity does not exist.
925        The update_entity operation replaces the entire entity and can be used to
926        remove properties.
927
928        :param str table_name:
929            The name of the table containing the entity to update.
930        :param entity:
931            The entity to update. Could be a dict or an entity object.
932            Must contain a PartitionKey and a RowKey.
933        :type entity: a dict or :class:`~azure.storage.table.models.Entity`
934        :param str if_match:
935            The client may specify the ETag for the entity on the
936            request in order to compare to the ETag maintained by the service
937            for the purpose of optimistic concurrency. The update operation
938            will be performed only if the ETag sent by the client matches the
939            value maintained by the server, indicating that the entity has
940            not been modified since it was retrieved by the client. To force
941            an unconditional update, set If-Match to the wildcard character (*).
942        :param int timeout:
943            The server timeout, expressed in seconds.
944        :return: The etag of the entity.
945        :rtype: str
946        '''
947        _validate_not_none('table_name', table_name)
948        request = _update_entity(entity, if_match, self.require_encryption, self.key_encryption_key,
949                                 self.encryption_resolver_function)
950        request.host_locations = self._get_host_locations()
951        request.path = _get_entity_path(table_name, entity['PartitionKey'], entity['RowKey'])
952        request.query['timeout'] = _int_to_str(timeout)
953
954        return self._perform_request(request, _extract_etag)
955
956    def merge_entity(self, table_name, entity, if_match='*', timeout=None):
957        '''
958        Updates an existing entity by merging the entity's properties. Throws
959        if the entity does not exist.
960
961        This operation does not replace the existing entity as the update_entity
962        operation does. A property cannot be removed with merge_entity.
963
964        Any properties with null values are ignored. All other properties will be
965        updated or added.
966
967        :param str table_name:
968            The name of the table containing the entity to merge.
969        :param entity:
970            The entity to merge. Could be a dict or an entity object.
971            Must contain a PartitionKey and a RowKey.
972        :type entity: a dict or :class:`~azure.storage.table.models.Entity`
973        :param str if_match:
974            The client may specify the ETag for the entity on the
975            request in order to compare to the ETag maintained by the service
976            for the purpose of optimistic concurrency. The merge operation
977            will be performed only if the ETag sent by the client matches the
978            value maintained by the server, indicating that the entity has
979            not been modified since it was retrieved by the client. To force
980            an unconditional merge, set If-Match to the wildcard character (*).
981        :param int timeout:
982            The server timeout, expressed in seconds.
983        :return: The etag of the entity.
984        :rtype: str
985        '''
986
987        _validate_not_none('table_name', table_name)
988
989        request = _merge_entity(entity, if_match, self.require_encryption,
990                                self.key_encryption_key)
991        request.host_locations = self._get_host_locations()
992        request.query['timeout'] = _int_to_str(timeout)
993        request.path = _get_entity_path(table_name, entity['PartitionKey'], entity['RowKey'])
994
995        return self._perform_request(request, _extract_etag)
996
997    def delete_entity(self, table_name, partition_key, row_key,
998                      if_match='*', timeout=None):
999        '''
1000        Deletes an existing entity in a table. Throws if the entity does not exist.
1001
1002        When an entity is successfully deleted, the entity is immediately marked
1003        for deletion and is no longer accessible to clients. The entity is later
1004        removed from the Table service during garbage collection.
1005
1006        :param str table_name:
1007            The name of the table containing the entity to delete.
1008        :param str partition_key:
1009            The PartitionKey of the entity.
1010        :param str row_key:
1011            The RowKey of the entity.
1012        :param str if_match:
1013            The client may specify the ETag for the entity on the
1014            request in order to compare to the ETag maintained by the service
1015            for the purpose of optimistic concurrency. The delete operation
1016            will be performed only if the ETag sent by the client matches the
1017            value maintained by the server, indicating that the entity has
1018            not been modified since it was retrieved by the client. To force
1019            an unconditional delete, set If-Match to the wildcard character (*).
1020        :param int timeout:
1021            The server timeout, expressed in seconds.
1022        '''
1023        _validate_not_none('table_name', table_name)
1024        request = _delete_entity(partition_key, row_key, if_match)
1025        request.host_locations = self._get_host_locations()
1026        request.query['timeout'] = _int_to_str(timeout)
1027        request.path = _get_entity_path(table_name, partition_key, row_key)
1028
1029        self._perform_request(request)
1030
1031    def insert_or_replace_entity(self, table_name, entity, timeout=None):
1032        '''
1033        Replaces an existing entity or inserts a new entity if it does not
1034        exist in the table. Because this operation can insert or update an
1035        entity, it is also known as an "upsert" operation.
1036
1037        If insert_or_replace_entity is used to replace an entity, any properties
1038        from the previous entity will be removed if the new entity does not define
1039        them.
1040
1041        :param str table_name:
1042            The name of the table in which to insert or replace the entity.
1043        :param entity:
1044            The entity to insert or replace. Could be a dict or an entity object.
1045            Must contain a PartitionKey and a RowKey.
1046        :type entity: a dict or :class:`~azure.storage.table.models.Entity`
1047        :param int timeout:
1048            The server timeout, expressed in seconds.
1049        :return: The etag of the entity.
1050        :rtype: str
1051        '''
1052        _validate_not_none('table_name', table_name)
1053        request = _insert_or_replace_entity(entity, self.require_encryption, self.key_encryption_key,
1054                                 self.encryption_resolver_function)
1055        request.host_locations = self._get_host_locations()
1056        request.query['timeout'] = _int_to_str(timeout)
1057        request.path = _get_entity_path(table_name, entity['PartitionKey'], entity['RowKey'])
1058
1059        return self._perform_request(request, _extract_etag)
1060
1061    def insert_or_merge_entity(self, table_name, entity, timeout=None):
1062        '''
1063        Merges an existing entity or inserts a new entity if it does not exist
1064        in the table.
1065
1066        If insert_or_merge_entity is used to merge an entity, any properties from
1067        the previous entity will be retained if the request does not define or
1068        include them.
1069
1070        :param str table_name:
1071            The name of the table in which to insert or merge the entity.
1072        :param entity:
1073            The entity to insert or merge. Could be a dict or an entity object.
1074            Must contain a PartitionKey and a RowKey.
1075        :type entity: a dict or :class:`~azure.storage.table.models.Entity`
1076        :param int timeout:
1077            The server timeout, expressed in seconds.
1078        :return: The etag of the entity.
1079        :rtype: str
1080        '''
1081
1082        _validate_not_none('table_name', table_name)
1083        request = _insert_or_merge_entity(entity, self.require_encryption,
1084                                          self.key_encryption_key)
1085        request.host_locations = self._get_host_locations()
1086        request.query['timeout'] = _int_to_str(timeout)
1087        request.path = _get_entity_path(table_name, entity['PartitionKey'], entity['RowKey'])
1088
1089        return self._perform_request(request, _extract_etag)
1090
1091    def _perform_request(self, request, parser=None, parser_args=None, operation_context=None):
1092        _update_storage_table_header(request)
1093        return super(TableService, self)._perform_request(request, parser, parser_args, operation_context)