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