diff --git a/sdk/cosmos/azure-cosmos/README.md b/sdk/cosmos/azure-cosmos/README.md index 7d0e5721d3ce..5344752a2bae 100644 --- a/sdk/cosmos/azure-cosmos/README.md +++ b/sdk/cosmos/azure-cosmos/README.md @@ -63,7 +63,7 @@ export ACCOUNT_KEY=$(az cosmosdb list-keys --resource-group $RES_GROUP --name $A Once you've populated the `ACCOUNT_URI` and `ACCOUNT_KEY` environment variables, you can create the [CosmosClient][ref_cosmosclient]. ```Python -from azure.cosmos import CosmosClient, PartitionKey, errors +from azure.cosmos import CosmosClient, PartitionKey, exceptions import os url = os.environ['ACCOUNT_URI'] @@ -104,7 +104,7 @@ After authenticating your [CosmosClient][ref_cosmosclient], you can work with an database_name = 'testDatabase' try: database = client.create_database(database_name) -except errors.CosmosResourceExistsError: +except exceptions.CosmosResourceExistsError: database = client.get_database_client(database_name) ``` @@ -116,9 +116,9 @@ This example creates a container with default settings. If a container with the container_name = 'products' try: container = database.create_container(id=container_name, partition_key=PartitionKey(path="/productName")) -except errors.CosmosResourceExistsError: +except exceptions.CosmosResourceExistsError: container = database.get_container_client(container_name) -except errors.CosmosHttpResponseError: +except exceptions.CosmosHttpResponseError: raise ``` @@ -264,7 +264,7 @@ the client level to enable it for all requests. ### General -When you interact with Cosmos DB using the Python SDK, errors returned by the service correspond to the same HTTP status codes returned for REST API requests: +When you interact with Cosmos DB using the Python SDK, exceptions returned by the service correspond to the same HTTP status codes returned for REST API requests: [HTTP Status Codes for Azure Cosmos DB][cosmos_http_status_codes] @@ -273,7 +273,7 @@ For example, if you try to create a container using an ID (name) that's already ```Python try: database.create_container(id=container_name, partition_key=PartitionKey(path="/productName") -except errors.CosmosResourceExistsError: +except exceptions.CosmosResourceExistsError: print("""Error creating container HTTP status code 409: The ID (name) provided for the container is already in use. The container name must be unique within the database.""") @@ -305,23 +305,22 @@ For more extensive documentation on the Cosmos DB service, see the [Azure Cosmos [cosmos_sql_queries]: https://docs.microsoft.com/azure/cosmos-db/how-to-sql-query [cosmos_ttl]: https://docs.microsoft.com/azure/cosmos-db/time-to-live [python]: https://www.python.org/downloads/ -[ref_container_delete_item]: https://azure.github.io/azure-sdk-for-python/ref/azure.cosmos.html#azure.cosmos.ContainerProxy.delete_item -[ref_container_query_items]: https://azure.github.io/azure-sdk-for-python/ref/azure.cosmos.html#azure.cosmos.ContainerProxy.query_items -[ref_container_upsert_item]: https://azure.github.io/azure-sdk-for-python/ref/azure.cosmos.html#azure.cosmos.ContainerProxy.upsert_item -[ref_container]: https://azure.github.io/azure-sdk-for-python/ref/azure.cosmos.html#azure.cosmos.ContainerProxy -[ref_cosmos_sdk]: https://azure.github.io/azure-sdk-for-python/ref/azure.cosmos.html -[ref_cosmosclient_create_database]: https://azure.github.io/azure-sdk-for-python/ref/azure.cosmos.html#azure.cosmos.CosmosClient.create_database -[ref_cosmosclient]: https://azure.github.io/azure-sdk-for-python/ref/azure.cosmos.html#azure.cosmos.CosmosClient -[ref_database]: https://azure.github.io/azure-sdk-for-python/ref/azure.cosmos.html#azure.cosmos.DatabaseProxy -[ref_httpfailure]: https://azure.github.io/azure-sdk-for-python/ref/azure.cosmos.errors.html#azure.cosmos.errors.CosmosHttpResponseError -[sample_database_mgmt]: https://github.com/Azure/azure-sdk-for-python/tree/master/sdk/cosmos/azure-cosmos/samples/DatabaseManagement -[sample_document_mgmt]: https://github.com/Azure/azure-sdk-for-python/tree/master/sdk/cosmos/azure-cosmos/samples/DocumentManagement +[ref_container_delete_item]: https://azuresdkdocs.blob.core.windows.net/$web/python/azure-cosmos/4.0.0b5/azure.cosmos.html#azure.cosmos.ContainerProxy.delete_item +[ref_container_query_items]: https://azuresdkdocs.blob.core.windows.net/$web/python/azure-cosmos/4.0.0b5/azure.cosmos.html#azure.cosmos.ContainerProxy.query_items +[ref_container_upsert_item]: https://azuresdkdocs.blob.core.windows.net/$web/python/azure-cosmos/4.0.0b5/azure.cosmos.html#azure.cosmos.ContainerProxy.upsert_item +[ref_container]: https://azuresdkdocs.blob.core.windows.net/$web/python/azure-cosmos/4.0.0b5/azure.cosmos.html#azure.cosmos.ContainerProxy +[ref_cosmos_sdk]: https://azuresdkdocs.blob.core.windows.net/$web/python/azure-cosmos/4.0.0b5/azure.cosmos.html +[ref_cosmosclient_create_database]: https://azuresdkdocs.blob.core.windows.net/$web/python/azure-cosmos/4.0.0b5/azure.cosmos.html#azure.cosmos.CosmosClient.create_database +[ref_cosmosclient]: https://azuresdkdocs.blob.core.windows.net/$web/python/azure-cosmos/4.0.0b5/azure.cosmos.html#azure.cosmos.CosmosClient +[ref_database]: https://azuresdkdocs.blob.core.windows.net/$web/python/azure-cosmos/4.0.0b5/azure.cosmos.html#azure.cosmos.DatabaseProxy +[ref_httpfailure]: https://azuresdkdocs.blob.core.windows.net/$web/python/azure-cosmos/4.0.0b5/azure.cosmos.html#azure.cosmos.exceptions.CosmosHttpResponseError +[sample_database_mgmt]: https://github.com/Azure/azure-sdk-for-python/tree/master/sdk/cosmos/azure-cosmos/samples/database_management.py +[sample_document_mgmt]: https://github.com/Azure/azure-sdk-for-python/tree/master/sdk/cosmos/azure-cosmos/samples/document_management.py [sample_examples_misc]: https://github.com/Azure/azure-sdk-for-python/tree/master/sdk/cosmos/azure-cosmos/samples/examples.py [source_code]: https://github.com/Azure/azure-sdk-for-python/tree/master/sdk/cosmos/azure-cosmos [venv]: https://docs.python.org/3/library/venv.html [virtualenv]: https://virtualenv.pypa.io - # Contributing This project welcomes contributions and suggestions. Most contributions require you to agree to a diff --git a/sdk/cosmos/azure-cosmos/azure/cosmos/__init__.py b/sdk/cosmos/azure-cosmos/azure/cosmos/__init__.py index 1f73d170e3c4..406418f1e0cc 100644 --- a/sdk/cosmos/azure-cosmos/azure/cosmos/__init__.py +++ b/sdk/cosmos/azure-cosmos/azure/cosmos/__init__.py @@ -19,6 +19,7 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. +from ._version import VERSION from ._retry_utility import ConnectionRetryPolicy from .container import ContainerProxy from .cosmos_client import CosmosClient @@ -40,7 +41,6 @@ ) from .partition_key import PartitionKey from .permission import Permission -from .version import VERSION __all__ = ( "CosmosClient", diff --git a/sdk/cosmos/azure-cosmos/azure/cosmos/_base.py b/sdk/cosmos/azure-cosmos/azure/cosmos/_base.py index 265d108e178b..5b866e541158 100644 --- a/sdk/cosmos/azure-cosmos/azure/cosmos/_base.py +++ b/sdk/cosmos/azure-cosmos/azure/cosmos/_base.py @@ -32,6 +32,8 @@ import six from six.moves.urllib.parse import quote as urllib_quote +from azure.core import MatchConditions + from . import auth from . import documents from . import partition_key @@ -63,9 +65,38 @@ 'continuation': 'continuation', 'is_start_from_beginning': 'isStartFromBeginning', 'populate_partition_key_range_statistics': 'populatePartitionKeyRangeStatistics', - 'populate_quota_info': 'populateQuotaInfo' + 'populate_quota_info': 'populateQuotaInfo', + 'content_type': 'contentType', + 'is_query_plan_request': 'isQueryPlanRequest', + 'supported_query_features': 'supportedQueryFeatures', + 'query_version': 'queryVersion' } +def _get_match_headers(kwargs): + # type: (Dict[str, Any]) -> Tuple(Optional[str], Optional[str]) + if_match = kwargs.pop('if_match', None) + if_none_match = kwargs.pop('if_none_match', None) + match_condition = kwargs.pop('match_condition', None) + if match_condition == MatchConditions.IfNotModified: + if_match = kwargs.pop('etag', None) + if not if_match: + raise ValueError("'match_condition' specified without 'etag'.") + elif match_condition == MatchConditions.IfPresent: + if_match = '*' + elif match_condition == MatchConditions.IfModified: + if_none_match = kwargs.pop('etag', None) + if not if_none_match: + raise ValueError("'match_condition' specified without 'etag'.") + elif match_condition == MatchConditions.IfMissing: + if_none_match = '*' + elif match_condition is None: + if 'etag' in kwargs: + raise ValueError("'etag' specified without 'match_condition'.") + else: + raise TypeError("Invalid match condition: {}".format(match_condition)) + return if_match, if_none_match + + def build_options(kwargs): # type: (Dict[str, Any]) -> Dict[str, Any] options = kwargs.pop('request_options', kwargs.pop('feed_options', {})) @@ -73,10 +104,11 @@ def build_options(kwargs): if key in kwargs: options[value] = kwargs.pop(key) - if 'if_match' in kwargs: - options['accessCondition'] = {'type': 'IfMatch', 'condition': kwargs.pop('if_match')} - if 'if_none_match' in kwargs: - options['accessCondition'] = {'type': 'IfNoneMatch', 'condition': kwargs.pop('if_none_match')} + if_match, if_none_match = _get_match_headers(kwargs) + if if_match: + options['accessCondition'] = {'type': 'IfMatch', 'condition': if_match} + if if_none_match: + options['accessCondition'] = {'type': 'IfNoneMatch', 'condition': if_none_match} return options @@ -178,6 +210,18 @@ def GetHeaders( # pylint: disable=too-many-statements,too-many-branches if options.get("offerThroughput"): headers[http_constants.HttpHeaders.OfferThroughput] = options["offerThroughput"] + if options.get("contentType"): + headers[http_constants.HttpHeaders.ContentType] = options['contentType'] + + if options.get("isQueryPlanRequest"): + headers[http_constants.HttpHeaders.IsQueryPlanRequest] = options['isQueryPlanRequest'] + + if options.get("supportedQueryFeatures"): + headers[http_constants.HttpHeaders.SupportedQueryFeatures] = options['supportedQueryFeatures'] + + if options.get("queryVersion"): + headers[http_constants.HttpHeaders.QueryVersion] = options['queryVersion'] + if "partitionKey" in options: # if partitionKey value is Undefined, serialize it as [{}] to be consistent with other SDKs. if options.get("partitionKey") is partition_key._Undefined: diff --git a/sdk/cosmos/azure-cosmos/azure/cosmos/_cosmos_client_connection.py b/sdk/cosmos/azure-cosmos/azure/cosmos/_cosmos_client_connection.py index 4fd82eeec093..e7bcc94dc978 100644 --- a/sdk/cosmos/azure-cosmos/azure/cosmos/_cosmos_client_connection.py +++ b/sdk/cosmos/azure-cosmos/azure/cosmos/_cosmos_client_connection.py @@ -2625,6 +2625,7 @@ def __QueryFeed( options=None, partition_key_range_id=None, response_hook=None, + is_query_plan=False, **kwargs ): """Query for more than one Azure Cosmos resources. @@ -2639,6 +2640,9 @@ def __QueryFeed( The request options for the request. :param str partition_key_range_id: Specifies partition key range id. + :param function response_hook: + :param bool is_query_plan: + Specififes if the call is to fetch query plan :rtype: list @@ -2664,7 +2668,8 @@ def __GetBodiesFromQueryResult(result): # Copy to make sure that default_headers won't be changed. if query is None: # Query operations will use ReadEndpoint even though it uses GET(for feed requests) - request_params = _request_object.RequestObject(typ, documents._OperationType.ReadFeed) + request_params = _request_object.RequestObject(typ, + documents._OperationType.QueryPlan if is_query_plan else documents._OperationType.ReadFeed) headers = base.GetHeaders(self, initial_headers, "get", path, id_, typ, options, partition_key_range_id) result, self.last_response_headers = self.__Get(path, request_params, headers, **kwargs) if response_hook: @@ -2674,6 +2679,9 @@ def __GetBodiesFromQueryResult(result): query = self.__CheckAndUnifyQueryFormat(query) initial_headers[http_constants.HttpHeaders.IsQuery] = "true" + if not is_query_plan: + initial_headers[http_constants.HttpHeaders.IsQuery] = "true" + if ( self._query_compatibility_mode == CosmosClientConnection._QueryCompatibilityMode.Default or self._query_compatibility_mode == CosmosClientConnection._QueryCompatibilityMode.Query @@ -2694,6 +2702,36 @@ def __GetBodiesFromQueryResult(result): return __GetBodiesFromQueryResult(result) + def _GetQueryPlanThroughGateway(self, query, resource_link, **kwargs): + supported_query_features = (documents._QueryFeature.Aggregate + "," + + documents._QueryFeature.CompositeAggregate + "," + + documents._QueryFeature.Distinct + "," + + documents._QueryFeature.MultipleOrderBy + "," + + documents._QueryFeature.OffsetAndLimit + "," + + documents._QueryFeature.OrderBy + "," + + documents._QueryFeature.Top) + + options = { + "contentType": runtime_constants.MediaTypes.Json, + "isQueryPlanRequest": True, + "supportedQueryFeatures": supported_query_features, + "queryVersion": http_constants.Versions.QueryVersion + } + + resource_link = base.TrimBeginningAndEndingSlashes(resource_link) + path = base.GetPathFromLink(resource_link, "docs") + resource_id = base.GetResourceIdOrFullNameFromLink(resource_link) + + return self.__QueryFeed(path, + "docs", + resource_id, + lambda r: r, + None, + query, + options, + is_query_plan=True, + **kwargs) + def __CheckAndUnifyQueryFormat(self, query_body): """Checks and unifies the format of the query body. diff --git a/sdk/cosmos/azure-cosmos/azure/cosmos/_default_retry_policy.py b/sdk/cosmos/azure-cosmos/azure/cosmos/_default_retry_policy.py index 6b5e52769193..225c69f54775 100644 --- a/sdk/cosmos/azure-cosmos/azure/cosmos/_default_retry_policy.py +++ b/sdk/cosmos/azure-cosmos/azure/cosmos/_default_retry_policy.py @@ -57,7 +57,8 @@ def __init__(self, *args): def needsRetry(self, error_code): if error_code in DefaultRetryPolicy.CONNECTION_ERROR_CODES: if self.args: - if (self.args[3].method == "GET") or (http_constants.HttpHeaders.IsQuery in self.args[3].headers): + if (self.args[3].method == "GET") or (http_constants.HttpHeaders.IsQuery in self.args[3].headers) \ + or (http_constants.HttpHeaders.IsQueryPlanRequest in self.args[3].headers): return True return False return True @@ -66,7 +67,7 @@ def needsRetry(self, error_code): def ShouldRetry(self, exception): """Returns true if should retry based on the passed-in exception. - :param (errors.CosmosHttpResponseError instance) exception: + :param (exceptions.CosmosHttpResponseError instance) exception: :rtype: boolean diff --git a/sdk/cosmos/azure-cosmos/azure/cosmos/_endpoint_discovery_retry_policy.py b/sdk/cosmos/azure-cosmos/azure/cosmos/_endpoint_discovery_retry_policy.py index 2f773de8735a..2ab110a5e8c8 100644 --- a/sdk/cosmos/azure-cosmos/azure/cosmos/_endpoint_discovery_retry_policy.py +++ b/sdk/cosmos/azure-cosmos/azure/cosmos/_endpoint_discovery_retry_policy.py @@ -61,7 +61,7 @@ def __init__(self, connection_policy, global_endpoint_manager, *args): def ShouldRetry(self, exception): # pylint: disable=unused-argument """Returns true if should retry based on the passed-in exception. - :param (errors.CosmosHttpResponseError instance) exception: + :param (exceptions.CosmosHttpResponseError instance) exception: :rtype: boolean diff --git a/sdk/cosmos/azure-cosmos/azure/cosmos/_execution_context/base_execution_context.py b/sdk/cosmos/azure-cosmos/azure/cosmos/_execution_context/base_execution_context.py index 8fec53bad54a..ee4981d06bd8 100644 --- a/sdk/cosmos/azure-cosmos/azure/cosmos/_execution_context/base_execution_context.py +++ b/sdk/cosmos/azure-cosmos/azure/cosmos/_execution_context/base_execution_context.py @@ -25,7 +25,6 @@ from collections import deque from .. import _retry_utility from .. import http_constants -from .. import _base # pylint: disable=protected-access @@ -171,100 +170,3 @@ def __init__(self, client, options, fetch_function): def _fetch_next_block(self): while super(_DefaultQueryExecutionContext, self)._has_more_pages() and not self._buffer: return self._fetch_items_helper_with_retries(self._fetch_function) - - -class _MultiCollectionQueryExecutionContext(_QueryExecutionContextBase): - """ - This class is used if it is client side partitioning - """ - - def __init__(self, client, options, database_link, query, partition_key): - """ - Constructor - :param CosmosClient client: - :param dict options: - The request options for the request. - :param str database_link: database self link or ID based link - :param (str or dict) query: - Partition_key (str): partition key for the query - - """ - super(_MultiCollectionQueryExecutionContext, self).__init__(client, options) - - self._current_collection_index = 0 - self._collection_links = [] - self._collection_links_length = 0 - - self._query = query - self._client = client - - partition_resolver = client.GetPartitionResolver(database_link) - - if partition_resolver is None: - raise ValueError(client.PartitionResolverErrorMessage) - - self._collection_links = partition_resolver.ResolveForRead(partition_key) - - self._collection_links_length = len(self._collection_links) - - if self._collection_links is None: - raise ValueError("_collection_links is None.") - - if self._collection_links_length <= 0: - raise ValueError("_collection_links_length is not greater than 0.") - - # Creating the QueryFeed for the first collection - path = _base.GetPathFromLink(self._collection_links[self._current_collection_index], "docs") - collection_id = _base.GetResourceIdOrFullNameFromLink(self._collection_links[self._current_collection_index]) - - self._current_collection_index += 1 - - def fetch_fn(options): - return client.QueryFeed(path, collection_id, query, options) - - self._fetch_function = fetch_fn - - def _has_more_pages(self): - return ( - not self._has_started - or self._continuation - or (self._collection_links and self._current_collection_index < self._collection_links_length) - ) - - def _fetch_next_block(self): - """Fetches the next block of query results. - - This iterates fetches the next block of results from the current collection link. - Once the current collection results were exhausted. It moves to the next collection link. - - :return: - List of fetched items. - :rtype: list - """ - # Fetch next block of results by executing the query against the current document collection - fetched_items = self._fetch_items_helper_with_retries(self._fetch_function) - - # If there are multiple document collections to query for(in case of partitioning), - # keep looping through each one of them, creating separate feed queries for each - # collection and fetching the items - while not fetched_items: - if self._collection_links and self._current_collection_index < self._collection_links_length: - path = _base.GetPathFromLink(self._collection_links[self._current_collection_index], "docs") - collection_id = _base.GetResourceIdOrFullNameFromLink( - self._collection_links[self._current_collection_index] - ) - - self._continuation = None - self._has_started = False - - def fetch_fn(options): - return self._client.QueryFeed(path, collection_id, self._query, options) - - self._fetch_function = fetch_fn - - fetched_items = self._fetch_items_helper_with_retries(self._fetch_function) - self._current_collection_index += 1 - else: - break - - return fetched_items diff --git a/sdk/cosmos/azure-cosmos/azure/cosmos/_execution_context/endpoint_component.py b/sdk/cosmos/azure-cosmos/azure/cosmos/_execution_context/endpoint_component.py index d876abaee8c0..254c4264224d 100644 --- a/sdk/cosmos/azure-cosmos/azure/cosmos/_execution_context/endpoint_component.py +++ b/sdk/cosmos/azure-cosmos/azure/cosmos/_execution_context/endpoint_component.py @@ -22,6 +22,10 @@ """Internal class for query execution endpoint component implementation in the Azure Cosmos database service. """ import numbers +import copy +import hashlib +import json +import six from azure.cosmos._execution_context.aggregators import ( _AverageAggregator, @@ -75,6 +79,86 @@ def next(self): raise StopIteration +class _QueryExecutionDistinctOrderedEndpointComponent(_QueryExecutionEndpointComponent): + """Represents an endpoint in handling distinct query. + + It returns only those values not already returned. + """ + def __init__(self, execution_context): + super(_QueryExecutionDistinctOrderedEndpointComponent, self).__init__(execution_context) + self.last_result = None + + def next(self): + res = next(self._execution_context) + while self.last_result == res: + res = next(self._execution_context) + self.last_result = res + return res + + +class _QueryExecutionDistinctUnorderedEndpointComponent(_QueryExecutionEndpointComponent): + """Represents an endpoint in handling distinct query. + + It returns only those values not already returned. + """ + def __init__(self, execution_context): + super(_QueryExecutionDistinctUnorderedEndpointComponent, self).__init__(execution_context) + self.last_result = set() + + def make_hash(self, value): + if isinstance(value, (set, tuple, list)): + return tuple([self.make_hash(v) for v in value]) + if not isinstance(value, dict): + if isinstance(value, numbers.Number): + return float(value) + return value + new_value = copy.deepcopy(value) + for k, v in new_value.items(): + new_value[k] = self.make_hash(v) + + return tuple(frozenset(sorted(new_value.items()))) + + def next(self): + res = next(self._execution_context) + + json_repr = json.dumps(self.make_hash(res)) + if six.PY3: + json_repr = json_repr.encode("utf-8") + + hash_object = hashlib.sha1(json_repr) + hashed_result = hash_object.hexdigest() + + while hashed_result in self.last_result: + res = next(self._execution_context) + json_repr = json.dumps(self.make_hash(res)) + if six.PY3: + json_repr = json_repr.encode("utf-8") + + hash_object = hashlib.sha1(json_repr) + hashed_result = hash_object.hexdigest() + self.last_result.add(hashed_result) + return res + + +class _QueryExecutionOffsetEndpointComponent(_QueryExecutionEndpointComponent): + """Represents an endpoint in handling offset query. + + It returns results offset by as many results as offset arg specified. + """ + def __init__(self, execution_context, offset_count): + super(_QueryExecutionOffsetEndpointComponent, self).__init__(execution_context) + self._offset_count = offset_count + + def next(self): + while self._offset_count > 0: + res = next(self._execution_context) + if res is not None: + self._offset_count -= 1 + else: + raise StopIteration + return next(self._execution_context) + + class _QueryExecutionAggregateEndpointComponent(_QueryExecutionEndpointComponent): """Represents an endpoint in handling aggregate query. diff --git a/sdk/cosmos/azure-cosmos/azure/cosmos/_execution_context/execution_dispatcher.py b/sdk/cosmos/azure-cosmos/azure/cosmos/_execution_context/execution_dispatcher.py index 2c37510a63a4..5884887c9d29 100644 --- a/sdk/cosmos/azure-cosmos/azure/cosmos/_execution_context/execution_dispatcher.py +++ b/sdk/cosmos/azure-cosmos/azure/cosmos/_execution_context/execution_dispatcher.py @@ -24,12 +24,13 @@ import json from six.moves import xrange -from azure.cosmos.errors import CosmosHttpResponseError +from azure.cosmos.exceptions import CosmosHttpResponseError +from azure.cosmos._execution_context import multi_execution_aggregator from azure.cosmos._execution_context.base_execution_context import _QueryExecutionContextBase from azure.cosmos._execution_context.base_execution_context import _DefaultQueryExecutionContext from azure.cosmos._execution_context.query_execution_info import _PartitionedQueryExecutionInfo from azure.cosmos._execution_context import endpoint_component -from azure.cosmos._execution_context import multi_execution_aggregator +from azure.cosmos.documents import _DistinctType from azure.cosmos.http_constants import StatusCodes, SubStatusCodes # pylint: disable=protected-access @@ -78,7 +79,9 @@ def next(self): return next(self._execution_context) except CosmosHttpResponseError as e: if _is_partitioned_execution_info(e): - query_execution_info = _get_partitioned_execution_info(e) + query_to_use = self._query if self._query is not None else "Select * from root r" + query_execution_info = _PartitionedQueryExecutionInfo(self._client._GetQueryPlanThroughGateway + (query_to_use, self._resource_link)) self._execution_context = self._create_pipelined_execution_context(query_execution_info) else: raise e @@ -99,7 +102,9 @@ def fetch_next_block(self): return self._execution_context.fetch_next_block() except CosmosHttpResponseError as e: if _is_partitioned_execution_info(e): - query_execution_info = _get_partitioned_execution_info(e) + query_to_use = self._query if self._query is not None else "Select * from root r" + query_execution_info = _PartitionedQueryExecutionInfo(self._client._GetQueryPlanThroughGateway + (query_to_use, self._resource_link)) self._execution_context = self._create_pipelined_execution_context(query_execution_info) else: raise e @@ -108,14 +113,20 @@ def fetch_next_block(self): def _create_pipelined_execution_context(self, query_execution_info): - assert self._resource_link, "code bug, resource_link has is required." - execution_context_aggregator = multi_execution_aggregator._MultiExecutionContextAggregator( - self._client, self._resource_link, self._query, self._options, query_execution_info - ) - return _PipelineExecutionContext( - self._client, self._options, execution_context_aggregator, query_execution_info - ) - + assert self._resource_link, "code bug, resource_link is required." + if query_execution_info.has_aggregates() and not query_execution_info.has_select_value(): + if self._options and ("enableCrossPartitionQuery" in self._options + and self._options["enableCrossPartitionQuery"]): + raise CosmosHttpResponseError(StatusCodes.BAD_REQUEST, + "Cross partition query only supports 'VALUE ' for aggregates") + + execution_context_aggregator = multi_execution_aggregator._MultiExecutionContextAggregator(self._client, + self._resource_link, + self._query, + self._options, + query_execution_info) + return _PipelineExecutionContext(self._client, self._options, execution_context_aggregator, + query_execution_info) class _PipelineExecutionContext(_QueryExecutionContextBase): # pylint: disable=abstract-method @@ -140,13 +151,28 @@ def __init__(self, client, options, execution_context, query_execution_info): if order_by: self._endpoint = endpoint_component._QueryExecutionOrderByEndpointComponent(self._endpoint) + aggregates = query_execution_info.get_aggregates() + if aggregates: + self._endpoint = endpoint_component._QueryExecutionAggregateEndpointComponent(self._endpoint, aggregates) + + offset = query_execution_info.get_offset() + if offset is not None: + self._endpoint = endpoint_component._QueryExecutionOffsetEndpointComponent(self._endpoint, offset) + top = query_execution_info.get_top() if top is not None: self._endpoint = endpoint_component._QueryExecutionTopEndpointComponent(self._endpoint, top) - aggregates = query_execution_info.get_aggregates() - if aggregates: - self._endpoint = endpoint_component._QueryExecutionAggregateEndpointComponent(self._endpoint, aggregates) + limit = query_execution_info.get_limit() + if limit is not None: + self._endpoint = endpoint_component._QueryExecutionTopEndpointComponent(self._endpoint, limit) + + distinct_type = query_execution_info.get_distinct_type() + if distinct_type != _DistinctType.NoneType: + if distinct_type == _DistinctType.Ordered: + self._endpoint = endpoint_component._QueryExecutionDistinctOrderedEndpointComponent(self._endpoint) + else: + self._endpoint = endpoint_component._QueryExecutionDistinctUnorderedEndpointComponent(self._endpoint) def next(self): """Returns the next query result. diff --git a/sdk/cosmos/azure-cosmos/azure/cosmos/_execution_context/query_execution_info.py b/sdk/cosmos/azure-cosmos/azure/cosmos/_execution_context/query_execution_info.py index 617d56c81e8a..6c1f717c8703 100644 --- a/sdk/cosmos/azure-cosmos/azure/cosmos/_execution_context/query_execution_info.py +++ b/sdk/cosmos/azure-cosmos/azure/cosmos/_execution_context/query_execution_info.py @@ -23,6 +23,7 @@ """ import six +from azure.cosmos.documents import _DistinctType class _PartitionedQueryExecutionInfo(object): @@ -32,7 +33,11 @@ class _PartitionedQueryExecutionInfo(object): """ QueryInfoPath = "queryInfo" + HasSelectValue = [QueryInfoPath, "hasSelectValue"] TopPath = [QueryInfoPath, "top"] + OffsetPath = [QueryInfoPath, "offset"] + LimitPath = [QueryInfoPath, "limit"] + DistinctTypePath = [QueryInfoPath, "distinctType"] OrderByPath = [QueryInfoPath, "orderBy"] AggregatesPath = [QueryInfoPath, "aggregates"] QueryRangesPath = "queryRanges" @@ -50,6 +55,21 @@ def get_top(self): """ return self._extract(_PartitionedQueryExecutionInfo.TopPath) + def get_limit(self): + """Returns the limit count (if any) or None + """ + return self._extract(_PartitionedQueryExecutionInfo.LimitPath) + + def get_offset(self): + """Returns the offset count (if any) or None + """ + return self._extract(_PartitionedQueryExecutionInfo.OffsetPath) + + def get_distinct_type(self): + """Returns the offset count (if any) or None + """ + return self._extract(_PartitionedQueryExecutionInfo.DistinctTypePath) + def get_order_by(self): """Returns order by items (if any) or None """ @@ -74,6 +94,32 @@ def get_rewritten_query(self): rewrittenQuery = rewrittenQuery.replace("{documentdb-formattableorderbyquery-filter}", "true") return rewrittenQuery + def has_select_value(self): + return self._extract(self.HasSelectValue) + + def has_top(self): + return self.get_top() is not None + + def has_limit(self): + return self.get_limit() is not None + + def has_offset(self): + return self.get_offset() is not None + + def has_distinct_type(self): + return self.get_distinct_type() != _DistinctType.NoneType + + def has_order_by(self): + order_by = self.get_order_by() + return order_by is not None and len(order_by) > 0 + + def has_aggregates(self): + aggregates = self.get_aggregates() + return aggregates is not None and len(aggregates) > 0 + + def has_rewritten_query(self): + return self.get_rewritten_query() is not None + def _extract(self, path): item = self._query_execution_info diff --git a/sdk/cosmos/azure-cosmos/azure/cosmos/_global_endpoint_manager.py b/sdk/cosmos/azure-cosmos/azure/cosmos/_global_endpoint_manager.py index acfab1059022..64964bfb1fcf 100644 --- a/sdk/cosmos/azure-cosmos/azure/cosmos/_global_endpoint_manager.py +++ b/sdk/cosmos/azure-cosmos/azure/cosmos/_global_endpoint_manager.py @@ -27,7 +27,7 @@ from six.moves.urllib.parse import urlparse from . import _constants as constants -from . import errors +from . import exceptions from ._location_cache import LocationCache # pylint: disable=protected-access @@ -126,13 +126,13 @@ def _GetDatabaseAccount(self, **kwargs): # specified (by creating a locational endpoint) and keeping eating the exception # until we get the database account and return None at the end, if we are not able # to get that info from any endpoints - except errors.CosmosHttpResponseError: + except exceptions.CosmosHttpResponseError: for location_name in self.PreferredLocations: locational_endpoint = _GlobalEndpointManager.GetLocationalEndpoint(self.DefaultEndpoint, location_name) try: database_account = self._GetDatabaseAccountStub(locational_endpoint, **kwargs) return database_account - except errors.CosmosHttpResponseError: + except exceptions.CosmosHttpResponseError: pass return None diff --git a/sdk/cosmos/azure-cosmos/azure/cosmos/_query_iterable.py b/sdk/cosmos/azure-cosmos/azure/cosmos/_query_iterable.py index d1cf600be217..76ee23451274 100644 --- a/sdk/cosmos/azure-cosmos/azure/cosmos/_query_iterable.py +++ b/sdk/cosmos/azure-cosmos/azure/cosmos/_query_iterable.py @@ -23,7 +23,6 @@ """ from azure.core.paging import PageIterator # type: ignore from azure.cosmos._execution_context import execution_dispatcher -from azure.cosmos._execution_context import base_execution_context # pylint: disable=protected-access @@ -54,7 +53,9 @@ def __init__( :param dict options: The request options for the request. :param method fetch_function: - :param str collection_link: + :param method resource_type: + The type of the resource being queried + :param str resource_link: If this is a Document query/feed collection_link is required. Example of `fetch_function`: @@ -73,20 +74,10 @@ def __init__( self._collection_link = collection_link self._database_link = database_link self._partition_key = partition_key - self._ex_context = self._create_execution_context() - super(QueryIterable, self).__init__(self._fetch_next, self._unpack, continuation_token=continuation_token) - - def _create_execution_context(self): - """instantiates the internal query execution context based. - """ - if self._database_link: - # client side partitioning query - return base_execution_context._MultiCollectionQueryExecutionContext( - self._client, self._options, self._database_link, self._query, self._partition_key - ) - return execution_dispatcher._ProxyQueryExecutionContext( + self._ex_context = execution_dispatcher._ProxyQueryExecutionContext( self._client, self._collection_link, self._query, self._options, self._fetch_function ) + super(QueryIterable, self).__init__(self._fetch_next, self._unpack, continuation_token=continuation_token) def _unpack(self, block): continuation = None diff --git a/sdk/cosmos/azure-cosmos/azure/cosmos/_resource_throttle_retry_policy.py b/sdk/cosmos/azure-cosmos/azure/cosmos/_resource_throttle_retry_policy.py index e21454ec7792..7abdbe27de5f 100644 --- a/sdk/cosmos/azure-cosmos/azure/cosmos/_resource_throttle_retry_policy.py +++ b/sdk/cosmos/azure-cosmos/azure/cosmos/_resource_throttle_retry_policy.py @@ -36,7 +36,7 @@ def __init__(self, max_retry_attempt_count, fixed_retry_interval_in_milliseconds def ShouldRetry(self, exception): """Returns true if should retry based on the passed-in exception. - :param (errors.CosmosHttpResponseError instance) exception: + :param (exceptions.CosmosHttpResponseError instance) exception: :rtype: boolean diff --git a/sdk/cosmos/azure-cosmos/azure/cosmos/_retry_utility.py b/sdk/cosmos/azure-cosmos/azure/cosmos/_retry_utility.py index ef67a7b69fbd..4360ab740c36 100644 --- a/sdk/cosmos/azure-cosmos/azure/cosmos/_retry_utility.py +++ b/sdk/cosmos/azure-cosmos/azure/cosmos/_retry_utility.py @@ -27,7 +27,7 @@ from azure.core.exceptions import AzureError, ClientAuthenticationError from azure.core.pipeline.policies import RetryPolicy -from . import errors +from . import exceptions from . import _endpoint_discovery_retry_policy from . import _resource_throttle_retry_policy from . import _default_retry_policy @@ -85,7 +85,7 @@ def Execute(client, global_endpoint_manager, function, *args, **kwargs): ] = resourceThrottle_retry_policy.cummulative_wait_time_in_milliseconds return result - except errors.CosmosHttpResponseError as e: + except exceptions.CosmosHttpResponseError as e: retry_policy = None if e.status_code == StatusCodes.FORBIDDEN and e.sub_status == SubStatusCodes.WRITE_FORBIDDEN: retry_policy = endpointDiscovery_retry_policy @@ -121,7 +121,7 @@ def Execute(client, global_endpoint_manager, function, *args, **kwargs): if client_timeout: kwargs['timeout'] = client_timeout - (time.time() - start_time) if kwargs['timeout'] <= 0: - raise errors.CosmosClientTimeoutError() + raise exceptions.CosmosClientTimeoutError() def ExecuteFunction(function, *args, **kwargs): @@ -134,7 +134,7 @@ def _configure_timeout(request, absolute, per_request): # type: (azure.core.pipeline.PipelineRequest, Optional[int], int) -> Optional[AzureError] if absolute is not None: if absolute <= 0: - raise errors.CosmosClientTimeoutError() + raise exceptions.CosmosClientTimeoutError() if per_request: # Both socket timeout and client timeout have been provided - use the shortest value. request.context.options['connection_timeout'] = min(per_request, absolute) @@ -161,7 +161,7 @@ def send(self, request): :return: Returns the PipelineResponse or raises error if maximum retries exceeded. :rtype: ~azure.core.pipeline.PipelineResponse :raises ~azure.core.exceptions.AzureError: Maximum retries exceeded. - :raises ~azure.cosmos.errors.CosmosClientTimeoutError: Specified timeout exceeded. + :raises ~azure.cosmos.exceptions.CosmosClientTimeoutError: Specified timeout exceeded. :raises ~azure.core.exceptions.ClientAuthenticationError: Authentication failed. """ absolute_timeout = request.context.options.pop('timeout', None) @@ -187,7 +187,7 @@ def send(self, request): # the authentication policy failed such that the client's request can't # succeed--we'll never have a response to it, so propagate the exception raise - except errors.CosmosClientTimeoutError as timeout_error: + except exceptions.CosmosClientTimeoutError as timeout_error: timeout_error.inner_exception = retry_error timeout_error.response = response timeout_error.history = retry_settings['history'] diff --git a/sdk/cosmos/azure-cosmos/azure/cosmos/_routing/routing_map_provider.py b/sdk/cosmos/azure-cosmos/azure/cosmos/_routing/routing_map_provider.py index 4d32c3aee167..dbbdd227cf2d 100644 --- a/sdk/cosmos/azure-cosmos/azure/cosmos/_routing/routing_map_provider.py +++ b/sdk/cosmos/azure-cosmos/azure/cosmos/_routing/routing_map_provider.py @@ -93,7 +93,7 @@ def _second_range_is_after_first_range(range1, range2): ##r.min < #previous_r.max return False - if range2.min == range2.min and range1.isMaxInclusive and range2.isMinInclusive: + if range2.min == range1.max and range1.isMaxInclusive and range2.isMinInclusive: # the inclusive ending endpoint of previous_r is the same as the inclusive beginning endpoint of r return False diff --git a/sdk/cosmos/azure-cosmos/azure/cosmos/_session.py b/sdk/cosmos/azure-cosmos/azure/cosmos/_session.py index dd1a573eaa4a..4b31eb59d857 100644 --- a/sdk/cosmos/azure-cosmos/azure/cosmos/_session.py +++ b/sdk/cosmos/azure-cosmos/azure/cosmos/_session.py @@ -29,7 +29,7 @@ from . import _base from . import http_constants from ._vector_session_token import VectorSessionToken -from .errors import CosmosHttpResponseError +from .exceptions import CosmosHttpResponseError class SessionContainer(object): diff --git a/sdk/cosmos/azure-cosmos/azure/cosmos/_session_retry_policy.py b/sdk/cosmos/azure-cosmos/azure/cosmos/_session_retry_policy.py index 01ae7778a7f4..a0b3718a1652 100644 --- a/sdk/cosmos/azure-cosmos/azure/cosmos/_session_retry_policy.py +++ b/sdk/cosmos/azure-cosmos/azure/cosmos/_session_retry_policy.py @@ -62,7 +62,7 @@ def __init__(self, endpoint_discovery_enable, global_endpoint_manager, *args): def ShouldRetry(self, _exception): """Returns true if should retry based on the passed-in exception. - :param (errors.CosmosHttpResponseError instance) exception: + :param (exceptions.CosmosHttpResponseError instance) exception: :rtype: boolean diff --git a/sdk/cosmos/azure-cosmos/azure/cosmos/_synchronized_request.py b/sdk/cosmos/azure-cosmos/azure/cosmos/_synchronized_request.py index f697f72f66c8..00716020cc97 100644 --- a/sdk/cosmos/azure-cosmos/azure/cosmos/_synchronized_request.py +++ b/sdk/cosmos/azure-cosmos/azure/cosmos/_synchronized_request.py @@ -30,7 +30,7 @@ from azure.core.exceptions import DecodeError # type: ignore from . import documents -from . import errors +from . import exceptions from . import http_constants from . import _retry_utility @@ -103,7 +103,7 @@ def _Request(global_endpoint_manager, request_params, connection_policy, pipelin if client_timeout is not None: kwargs['timeout'] = client_timeout - (time.time() - start_time) if kwargs['timeout'] <= 0: - raise errors.CosmosClientTimeoutError() + raise exceptions.CosmosClientTimeoutError() if request_params.endpoint_override: base_url = request_params.endpoint_override @@ -161,13 +161,13 @@ def _Request(global_endpoint_manager, request_params, connection_policy, pipelin data = data.decode("utf-8") if response.status_code == 404: - raise errors.CosmosResourceNotFoundError(message=data, response=response) + raise exceptions.CosmosResourceNotFoundError(message=data, response=response) if response.status_code == 409: - raise errors.CosmosResourceExistsError(message=data, response=response) + raise exceptions.CosmosResourceExistsError(message=data, response=response) if response.status_code == 412: - raise errors.CosmosAccessConditionFailedError(message=data, response=response) + raise exceptions.CosmosAccessConditionFailedError(message=data, response=response) if response.status_code >= 400: - raise errors.CosmosHttpResponseError(message=data, response=response) + raise exceptions.CosmosHttpResponseError(message=data, response=response) result = None if is_media: diff --git a/sdk/cosmos/azure-cosmos/azure/cosmos/_utils.py b/sdk/cosmos/azure-cosmos/azure/cosmos/_utils.py index f44e3a906bda..b6b6f0e18d4a 100644 --- a/sdk/cosmos/azure-cosmos/azure/cosmos/_utils.py +++ b/sdk/cosmos/azure-cosmos/azure/cosmos/_utils.py @@ -24,7 +24,7 @@ import platform import re -from .version import VERSION +from ._version import VERSION def get_user_agent(): diff --git a/sdk/cosmos/azure-cosmos/azure/cosmos/_vector_session_token.py b/sdk/cosmos/azure-cosmos/azure/cosmos/_vector_session_token.py index 675f0801632d..378377efeddc 100644 --- a/sdk/cosmos/azure-cosmos/azure/cosmos/_vector_session_token.py +++ b/sdk/cosmos/azure-cosmos/azure/cosmos/_vector_session_token.py @@ -22,7 +22,7 @@ """Session Consistency Tracking in the Azure Cosmos database service. """ -from . import errors +from . import exceptions from .http_constants import StatusCodes @@ -120,7 +120,7 @@ def merge(self, other): raise ValueError("Invalid Session Token (should not be None)") if self.version == other.version and len(self.local_lsn_by_region) != len(other.local_lsn_by_region): - raise errors.CosmosHttpResponseError( + raise exceptions.CosmosHttpResponseError( status_code=StatusCodes.INTERNAL_SERVER_ERROR, message=("Compared session tokens '%s' and '%s' have unexpected regions." % (self.session_token, other.session_token)) @@ -147,7 +147,7 @@ def merge(self, other): if local_lsn2 is not None: highest_local_lsn_by_region[region_id] = max(local_lsn1, local_lsn2) elif self.version == other.version: - raise errors.CosmosHttpResponseError( + raise exceptions.CosmosHttpResponseError( status_code=StatusCodes.INTERNAL_SERVER_ERROR, message=("Compared session tokens '%s' and '%s' have unexpected regions." % (self.session_token, other.session_token)) diff --git a/sdk/cosmos/azure-cosmos/azure/cosmos/version.py b/sdk/cosmos/azure-cosmos/azure/cosmos/_version.py similarity index 100% rename from sdk/cosmos/azure-cosmos/azure/cosmos/version.py rename to sdk/cosmos/azure-cosmos/azure/cosmos/_version.py diff --git a/sdk/cosmos/azure-cosmos/azure/cosmos/container.py b/sdk/cosmos/azure-cosmos/azure/cosmos/container.py index 73441d19f5ab..e2853c059272 100644 --- a/sdk/cosmos/azure-cosmos/azure/cosmos/container.py +++ b/sdk/cosmos/azure-cosmos/azure/cosmos/container.py @@ -29,7 +29,7 @@ from ._cosmos_client_connection import CosmosClientConnection from ._base import build_options -from .errors import CosmosResourceNotFoundError +from .exceptions import CosmosResourceNotFoundError from .http_constants import StatusCodes from .offer import Offer from .scripts import ScriptsProxy @@ -67,6 +67,10 @@ def __init__(self, client_connection, database_link, id, properties=None): # py self._is_system_key = None self._scripts = None # type: Optional[ScriptsProxy] + def __repr__(self): + # type () -> str + return "".format(self.container_link)[:1024] + def _get_properties(self): # type: () -> Dict[str, Any] if self._properties is None: @@ -116,18 +120,16 @@ def read( **kwargs # type: Any ): # type: (...) -> Dict[str, Any] - """ - Read the container properties + """Read the container properties. - :param session_token: Token for use with Session consistency. - :param initial_headers: Initial headers to be sent as part of the request. :param populate_query_metrics: Enable returning query metrics in response headers. :param populate_partition_key_range_statistics: Enable returning partition key range statistics in response headers. :param populate_quota_info: Enable returning collection storage quota information in response headers. - :param request_options: Dictionary of additional properties to be used for the request. - :param response_hook: a callable invoked with the response metadata - :raises ~azure.cosmos.errors.CosmosHttpResponseError: Raised if the container couldn't be retrieved. + :keyword str session_token: Token for use with Session consistency. + :keyword dict[str,str] initial_headers: Initial headers to be sent as part of the request. + :keyword Callable response_hook: A callable invoked with the response metadata. + :raises ~azure.cosmos.exceptions.CosmosHttpResponseError: Raised if the container couldn't be retrieved. This includes if the container does not exist. :returns: Dict representing the retrieved container. :rtype: dict[str, Any] @@ -161,19 +163,17 @@ def read_item( **kwargs # type: Any ): # type: (...) -> Dict[str, str] - """ - Get the item identified by `item`. + """Get the item identified by `item`. :param item: The ID (name) or dict representing item to retrieve. :param partition_key: Partition key for the item to retrieve. - :param session_token: Token for use with Session consistency. - :param initial_headers: Initial headers to be sent as part of the request. :param populate_query_metrics: Enable returning query metrics in response headers. :param post_trigger_include: trigger id to be used as post operation trigger. - :param request_options: Dictionary of additional properties to be used for the request. - :param response_hook: a callable invoked with the response metadata + :keyword str session_token: Token for use with Session consistency. + :keyword dict[str,str] initial_headers: Initial headers to be sent as part of the request. + :keyword Callable response_hook: A callable invoked with the response metadata. :returns: Dict representing the item to be retrieved. - :raises ~azure.cosmos.errors.CosmosHttpResponseError: The given item couldn't be retrieved. + :raises ~azure.cosmos.exceptions.CosmosHttpResponseError: The given item couldn't be retrieved. :rtype: dict[str, Any] .. admonition:: Example: @@ -190,11 +190,11 @@ def read_item( request_options = build_options(kwargs) response_hook = kwargs.pop('response_hook', None) - if partition_key: + if partition_key is not None: request_options["partitionKey"] = self._set_partition_key(partition_key) if populate_query_metrics is not None: request_options["populateQueryMetrics"] = populate_query_metrics - if post_trigger_include: + if post_trigger_include is not None: request_options["postTriggerInclude"] = post_trigger_include result = self.client_connection.ReadItem(document_link=doc_link, options=request_options, **kwargs) @@ -210,15 +210,13 @@ def read_all_items( **kwargs # type: Any ): # type: (...) -> Iterable[Dict[str, Any]] - """ - List all items in the container. + """List all items in the container. :param max_item_count: Max number of items to be returned in the enumeration operation. - :param session_token: Token for use with Session consistency. - :param initial_headers: Initial headers to be sent as part of the request. :param populate_query_metrics: Enable returning query metrics in response headers. - :param feed_options: Dictionary of additional properties to be used for the request. - :param response_hook: a callable invoked with the response metadata + :keyword str session_token: Token for use with Session consistency. + :keyword dict[str,str] initial_headers: Initial headers to be sent as part of the request. + :keyword Callable response_hook: A callable invoked with the response metadata. :returns: An Iterable of items (dicts). :rtype: Iterable[dict[str, Any]] """ @@ -249,8 +247,7 @@ def query_items_change_feed( **kwargs # type: Any ): # type: (...) -> Iterable[Dict[str, Any]] - """ - Get a sorted list of items that were changed, in the order in which they were modified. + """Get a sorted list of items that were changed, in the order in which they were modified. :param partition_key_range_id: ChangeFeed requests can be executed against specific partition key ranges. This is used to process the change feed in parallel across multiple consumers. @@ -258,8 +255,7 @@ def query_items_change_feed( beginning (true) or from current (false). By default it's start from current (false). :param continuation: e_tag value to be used as continuation for reading change feed. :param max_item_count: Max number of items to be returned in the enumeration operation. - :param feed_options: Dictionary of additional properties to be used for the request. - :param response_hook: a callable invoked with the response metadata + :keyword Callable response_hook: A callable invoked with the response metadata. :returns: An Iterable of items (dicts). :rtype: Iterable[dict[str, Any]] """ @@ -297,8 +293,7 @@ def query_items( **kwargs # type: Any ): # type: (...) -> Iterable[Dict[str, Any]] - """ - Return all results matching the given `query`. + """Return all results matching the given `query`. You can use any value for the container name in the FROM clause, but typically the container name is used. In the examples below, the container name is "products," and is aliased as "p" for easier referencing @@ -311,13 +306,12 @@ def query_items( execute the query in the Azure Cosmos DB service. More than one request is necessary if the query is not scoped to single partition key value. :param max_item_count: Max number of items to be returned in the enumeration operation. - :param session_token: Token for use with Session consistency. - :param initial_headers: Initial headers to be sent as part of the request. :param enable_scan_in_query: Allow scan on the queries which couldn't be served as indexing was opted out on the requested paths. :param populate_query_metrics: Enable returning query metrics in response headers. - :param feed_options: Dictionary of additional properties to be used for the request. - :param response_hook: a callable invoked with the response metadata + :keyword str session_token: Token for use with Session consistency. + :keyword dict[str,str] initial_headers: Initial headers to be sent as part of the request. + :keyword Callable response_hook: A callable invoked with the response metadata. :returns: An Iterable of items (dicts). :rtype: Iterable[dict[str, Any]] @@ -378,21 +372,21 @@ def replace_item( **kwargs # type: Any ): # type: (...) -> Dict[str, str] - """ - Replaces the specified item if it exists in the container. + """Replaces the specified item if it exists in the container. :param item: The ID (name) or dict representing item to be replaced. :param body: A dict-like object representing the item to replace. - :param session_token: Token for use with Session consistency. - :param initial_headers: Initial headers to be sent as part of the request. - :param access_condition: Conditions Associated with the request. :param populate_query_metrics: Enable returning query metrics in response headers. :param pre_trigger_include: trigger id to be used as pre operation trigger. :param post_trigger_include: trigger id to be used as post operation trigger. - :param request_options: Dictionary of additional properties to be used for the request. - :param response_hook: a callable invoked with the response metadata + :keyword str session_token: Token for use with Session consistency. + :keyword dict[str,str] initial_headers: Initial headers to be sent as part of the request. + :keyword str etag: An ETag value, or the wildcard character (*). Used to check if the resource + has changed, and act according to the condition specified by the `match_condition` parameter. + :keyword ~azure.core.MatchConditions match_condition: The match condition to use upon the etag. + :keyword Callable response_hook: A callable invoked with the response metadata. :returns: A dict representing the item after replace went through. - :raises ~azure.cosmos.errors.CosmosHttpResponseError: The replace failed or the item with + :raises ~azure.cosmos.exceptions.CosmosHttpResponseError: The replace failed or the item with given id does not exist. :rtype: dict[str, Any] """ @@ -402,9 +396,9 @@ def replace_item( request_options["disableIdGeneration"] = True if populate_query_metrics is not None: request_options["populateQueryMetrics"] = populate_query_metrics - if pre_trigger_include: + if pre_trigger_include is not None: request_options["preTriggerInclude"] = pre_trigger_include - if post_trigger_include: + if post_trigger_include is not None: request_options["postTriggerInclude"] = post_trigger_include result = self.client_connection.ReplaceItem( @@ -424,21 +418,22 @@ def upsert_item( **kwargs # type: Any ): # type: (...) -> Dict[str, str] - """ - Insert or update the specified item. + """Insert or update the specified item. + If the item already exists in the container, it is replaced. If it does not, it is inserted. :param body: A dict-like object representing the item to update or insert. - :param session_token: Token for use with Session consistency. - :param initial_headers: Initial headers to be sent as part of the request. - :param access_condition: Conditions Associated with the request. :param populate_query_metrics: Enable returning query metrics in response headers. :param pre_trigger_include: trigger id to be used as pre operation trigger. :param post_trigger_include: trigger id to be used as post operation trigger. - :param request_options: Dictionary of additional properties to be used for the request. - :param response_hook: a callable invoked with the response metadata + :keyword str session_token: Token for use with Session consistency. + :keyword dict[str,str] initial_headers: Initial headers to be sent as part of the request. + :keyword str etag: An ETag value, or the wildcard character (*). Used to check if the resource + has changed, and act according to the condition specified by the `match_condition` parameter. + :keyword ~azure.core.MatchConditions match_condition: The match condition to use upon the etag. + :keyword Callable response_hook: A callable invoked with the response metadata. :returns: A dict representing the upserted item. - :raises ~azure.cosmos.errors.CosmosHttpResponseError: The given item could not be upserted. + :raises ~azure.cosmos.exceptions.CosmosHttpResponseError: The given item could not be upserted. :rtype: dict[str, Any] """ request_options = build_options(kwargs) @@ -446,9 +441,9 @@ def upsert_item( request_options["disableIdGeneration"] = True if populate_query_metrics is not None: request_options["populateQueryMetrics"] = populate_query_metrics - if pre_trigger_include: + if pre_trigger_include is not None: request_options["preTriggerInclude"] = pre_trigger_include - if post_trigger_include: + if post_trigger_include is not None: request_options["postTriggerInclude"] = post_trigger_include result = self.client_connection.UpsertItem( @@ -468,22 +463,23 @@ def create_item( **kwargs # type: Any ): # type: (...) -> Dict[str, str] - """ - Create an item in the container. + """Create an item in the container. + To update or replace an existing item, use the :func:`ContainerProxy.upsert_item` method. :param body: A dict-like object representing the item to create. - :param session_token: Token for use with Session consistency. - :param initial_headers: Initial headers to be sent as part of the request. - :param access_condition: Conditions Associated with the request. :param populate_query_metrics: Enable returning query metrics in response headers. :param pre_trigger_include: trigger id to be used as pre operation trigger. :param post_trigger_include: trigger id to be used as post operation trigger. :param indexing_directive: Indicate whether the document should be omitted from indexing. - :param request_options: Dictionary of additional properties to be used for the request. - :param response_hook: a callable invoked with the response metadata + :keyword str session_token: Token for use with Session consistency. + :keyword dict[str,str] initial_headers: Initial headers to be sent as part of the request. + :keyword str etag: An ETag value, or the wildcard character (*). Used to check if the resource + has changed, and act according to the condition specified by the `match_condition` parameter. + :keyword ~azure.core.MatchConditions match_condition: The match condition to use upon the etag. + :keyword Callable response_hook: A callable invoked with the response metadata. :returns: A dict representing the new item. - :raises ~azure.cosmos.errors.CosmosHttpResponseError: Item with the given ID already exists. + :raises ~azure.cosmos.exceptions.CosmosHttpResponseError: Item with the given ID already exists. :rtype: dict[str, Any] """ request_options = build_options(kwargs) @@ -492,11 +488,11 @@ def create_item( request_options["disableAutomaticIdGeneration"] = True if populate_query_metrics: request_options["populateQueryMetrics"] = populate_query_metrics - if pre_trigger_include: + if pre_trigger_include is not None: request_options["preTriggerInclude"] = pre_trigger_include - if post_trigger_include: + if post_trigger_include is not None: request_options["postTriggerInclude"] = post_trigger_include - if indexing_directive: + if indexing_directive is not None: request_options["indexingDirective"] = indexing_directive result = self.client_connection.CreateItem( @@ -517,32 +513,32 @@ def delete_item( **kwargs # type: Any ): # type: (...) -> None - """ - Delete the specified item from the container. + """Delete the specified item from the container. :param item: The ID (name) or dict representing item to be deleted. :param partition_key: Specifies the partition key value for the item. - :param session_token: Token for use with Session consistency. - :param initial_headers: Initial headers to be sent as part of the request. - :param access_condition: Conditions Associated with the request. :param populate_query_metrics: Enable returning query metrics in response headers. :param pre_trigger_include: trigger id to be used as pre operation trigger. :param post_trigger_include: trigger id to be used as post operation trigger. - :param request_options: Dictionary of additional properties to be used for the request. - :param response_hook: a callable invoked with the response metadata - :raises ~azure.cosmos.errors.CosmosHttpResponseError: The item wasn't deleted successfully. - :raises ~azure.cosmos.errors.CosmosResourceNotFoundError: The item does not exist in the container. + :keyword str session_token: Token for use with Session consistency. + :keyword dict[str,str] initial_headers: Initial headers to be sent as part of the request. + :keyword str etag: An ETag value, or the wildcard character (*). Used to check if the resource + has changed, and act according to the condition specified by the `match_condition` parameter. + :keyword ~azure.core.MatchConditions match_condition: The match condition to use upon the etag. + :keyword Callable response_hook: A callable invoked with the response metadata. + :raises ~azure.cosmos.exceptions.CosmosHttpResponseError: The item wasn't deleted successfully. + :raises ~azure.cosmos.exceptions.CosmosResourceNotFoundError: The item does not exist in the container. :rtype: None """ request_options = build_options(kwargs) response_hook = kwargs.pop('response_hook', None) - if partition_key: + if partition_key is not None: request_options["partitionKey"] = self._set_partition_key(partition_key) if populate_query_metrics is not None: request_options["populateQueryMetrics"] = populate_query_metrics - if pre_trigger_include: + if pre_trigger_include is not None: request_options["preTriggerInclude"] = pre_trigger_include - if post_trigger_include: + if post_trigger_include is not None: request_options["postTriggerInclude"] = post_trigger_include document_link = self._get_document_link(item) @@ -553,12 +549,11 @@ def delete_item( @distributed_trace def read_offer(self, **kwargs): # type: (Any) -> Offer - """ - Read the Offer object for this container. + """Read the Offer object for this container. - :param response_hook: a callable invoked with the response metadata + :keyword Callable response_hook: A callable invoked with the response metadata. :returns: Offer for the container. - :raises ~azure.cosmos.errors.CosmosHttpResponseError: No offer exists for the container or + :raises ~azure.cosmos.exceptions.CosmosHttpResponseError: No offer exists for the container or the offer could not be retrieved. :rtype: ~azure.cosmos.Offer """ @@ -583,13 +578,12 @@ def read_offer(self, **kwargs): @distributed_trace def replace_throughput(self, throughput, **kwargs): # type: (int, Any) -> Offer - """ - Replace the container's throughput + """Replace the container's throughput. :param throughput: The throughput to be set (an integer). - :param response_hook: a callable invoked with the response metadata + :keyword Callable response_hook: A callable invoked with the response metadata. :returns: Offer for the container, updated with new throughput. - :raises ~azure.cosmos.errors.CosmosHttpResponseError: No offer exists for the container + :raises ~azure.cosmos.exceptions.CosmosHttpResponseError: No offer exists for the container or the offer could not be updated. :rtype: ~azure.cosmos.Offer """ @@ -617,12 +611,10 @@ def replace_throughput(self, throughput, **kwargs): @distributed_trace def list_conflicts(self, max_item_count=None, **kwargs): # type: (Optional[int], Any) -> Iterable[Dict[str, Any]] - """ - List all conflicts in the container. + """List all conflicts in the container. :param max_item_count: Max number of items to be returned in the enumeration operation. - :param feed_options: Dictionary of additional properties to be used for the request. - :param response_hook: a callable invoked with the response metadata + :keyword Callable response_hook: A callable invoked with the response metadata. :returns: An Iterable of conflicts (dicts). :rtype: Iterable[dict[str, Any]] """ @@ -649,18 +641,16 @@ def query_conflicts( **kwargs # type: Any ): # type: (...) -> Iterable[Dict[str, Any]] - """ - Return all conflicts matching the given `query`. + """Return all conflicts matching the given `query`. :param query: The Azure Cosmos DB SQL query to execute. :param parameters: Optional array of parameters to the query. Ignored if no query is provided. - :param partition_key: Specifies the partition key value for the item. :param enable_cross_partition_query: Allows sending of more than one request to execute the query in the Azure Cosmos DB service. More than one request is necessary if the query is not scoped to single partition key value. + :param partition_key: Specifies the partition key value for the item. :param max_item_count: Max number of items to be returned in the enumeration operation. - :param feed_options: Dictionary of additional properties to be used for the request. - :param response_hook: a callable invoked with the response metadata + :keyword Callable response_hook: A callable invoked with the response metadata. :returns: An Iterable of conflicts (dicts). :rtype: Iterable[dict[str, Any]] """ @@ -686,20 +676,18 @@ def query_conflicts( @distributed_trace def get_conflict(self, conflict, partition_key, **kwargs): # type: (Union[str, Dict[str, Any]], Any, Any) -> Dict[str, str] - """ - Get the conflict identified by `conflict`. + """Get the conflict identified by `conflict`. :param conflict: The ID (name) or dict representing the conflict to retrieve. :param partition_key: Partition key for the conflict to retrieve. - :param request_options: Dictionary of additional properties to be used for the request. - :param response_hook: a callable invoked with the response metadata + :keyword Callable response_hook: A callable invoked with the response metadata. :returns: A dict representing the retrieved conflict. - :raises ~azure.cosmos.errors.CosmosHttpResponseError: The given conflict couldn't be retrieved. + :raises ~azure.cosmos.exceptions.CosmosHttpResponseError: The given conflict couldn't be retrieved. :rtype: dict[str, Any] """ request_options = build_options(kwargs) response_hook = kwargs.pop('response_hook', None) - if partition_key: + if partition_key is not None: request_options["partitionKey"] = self._set_partition_key(partition_key) result = self.client_connection.ReadConflict( @@ -712,20 +700,18 @@ def get_conflict(self, conflict, partition_key, **kwargs): @distributed_trace def delete_conflict(self, conflict, partition_key, **kwargs): # type: (Union[str, Dict[str, Any]], Any, Any) -> None - """ - Delete the specified conflict from the container. + """Delete the specified conflict from the container. :param conflict: The ID (name) or dict representing the conflict to be deleted. :param partition_key: Partition key for the conflict to delete. - :param request_options: Dictionary of additional properties to be used for the request. - :param response_hook: a callable invoked with the response metadata - :raises ~azure.cosmos.errors.CosmosHttpResponseError: The conflict wasn't deleted successfully. - :raises ~azure.cosmos.errors.CosmosResourceNotFoundError: The conflict does not exist in the container. + :keyword Callable response_hook: A callable invoked with the response metadata. + :raises ~azure.cosmos.exceptions.CosmosHttpResponseError: The conflict wasn't deleted successfully. + :raises ~azure.cosmos.exceptions.CosmosResourceNotFoundError: The conflict does not exist in the container. :rtype: None """ request_options = build_options(kwargs) response_hook = kwargs.pop('response_hook', None) - if partition_key: + if partition_key is not None: request_options["partitionKey"] = self._set_partition_key(partition_key) result = self.client_connection.DeleteConflict( diff --git a/sdk/cosmos/azure-cosmos/azure/cosmos/cosmos_client.py b/sdk/cosmos/azure-cosmos/azure/cosmos/cosmos_client.py index c7065f8f8e5b..aeca4e68de5f 100644 --- a/sdk/cosmos/azure-cosmos/azure/cosmos/cosmos_client.py +++ b/sdk/cosmos/azure-cosmos/azure/cosmos/cosmos_client.py @@ -32,7 +32,7 @@ from ._retry_utility import ConnectionRetryPolicy from .database import DatabaseProxy from .documents import ConnectionPolicy, DatabaseAccount -from .errors import CosmosResourceNotFoundError +from .exceptions import CosmosResourceNotFoundError __all__ = ("CosmosClient",) @@ -126,53 +126,31 @@ class CosmosClient(object): Use this client to configure and execute requests to the Azure Cosmos DB service. :param str url: The URL of the Cosmos DB account. - :param credential: - Can be the account key, or a dictionary of resource tokens. + :param credential: Can be the account key, or a dictionary of resource tokens. :type credential: str or dict[str, str] - :param str consistency_level: - Consistency level to use for the session. The default value is "Session". - - **Keyword arguments:** - - *timeout* - An absolute timeout in seconds, for the combined HTTP request and response processing. - - *request_timeout* - The HTTP request timeout in seconds. - - *media_request_timeout* - The media request timeout in seconds. - - *connection_mode* - The connection mode for the client - currently only supports 'Gateway'. - - *media_read_mode* - The mode for use with downloading attachment content - default value is `Buffered`. - - *proxy_config* - Instance of `azure.cosmos.ProxyConfiguration`. - - *ssl_config* - Instance of `azure.cosmos.SSLConfiguration`. - - *connection_verify* - Whether to verify the connection, default value is True. - - *connection_cert* - An alternative certificate to verify the connection. - - *retry_total* - Maximum retry attempts. - - *retry_backoff_max* - Maximum retry wait time in seconds. - - *retry_fixed_interval* - Fixed retry interval in milliseconds. - - *retry_read* - Maximum number of socket read retry attempts. - - *retry_connect* - Maximum number of connection error retry attempts. - - *retry_status* - Maximum number of retry attempts on error status codes. - - *retry_on_status_codes* - A list of specific status codes to retry on. - - *retry_backoff_factor* - Factor to calculate wait time between retry attempts. - - *enable_endpoint_discovery* - Enable endpoint discovery for geo-replicated database accounts. Default is True. - - *preferred_locations* - The preferred locations for geo-replicated database accounts. - - *connection_policy* - An instance of `azure.cosmos.documents.ConnectionPolicy` + :param str consistency_level: Consistency level to use for the session. The default value is "Session". + :keyword int timeout: An absolute timeout in seconds, for the combined HTTP request and response processing. + :keyword int request_timeout: The HTTP request timeout in seconds. + :keyword int media_request_timeout: The media request timeout in seconds. + :keyword str connection_mode: The connection mode for the client - currently only supports 'Gateway'. + :keyword str media_read_mode: The mode for use with downloading attachment content - default value is `Buffered`. + :keyword proxy_config: Connection proxy configuration. + :paramtype proxy_config: ~azure.cosmos.ProxyConfiguration + :keyword ssl_config: Connection SSL configuration. + :paramtype ssl_config: ~azure.cosmos.SSLConfiguration + :keyword bool connection_verify: Whether to verify the connection, default value is True. + :keyword str connection_cert: An alternative certificate to verify the connection. + :keyword int retry_total: Maximum retry attempts. + :keyword int retry_backoff_max: Maximum retry wait time in seconds. + :keyword int retry_fixed_interval: Fixed retry interval in milliseconds. + :keyword int retry_read: Maximum number of socket read retry attempts. + :keyword int retry_connect: Maximum number of connection error retry attempts. + :keyword int retry_status: Maximum number of retry attempts on error status codes. + :keyword list[int] retry_on_status_codes: A list of specific status codes to retry on. + :keyword float retry_backoff_factor: Factor to calculate wait time between retry attempts. + :keyword bool enable_endpoint_discovery: Enable endpoint discovery for geo-replicated database accounts. + Default is True. + :keyword list[str] preferred_locations: The preferred locations for geo-replicated database accounts. .. admonition:: Example: @@ -194,6 +172,10 @@ def __init__(self, url, credential, consistency_level="Session", **kwargs): url, auth=auth, consistency_level=consistency_level, connection_policy=connection_policy, **kwargs ) + def __repr__(self): # pylint:disable=client-method-name-no-double-underscore + # type () -> str + return "".format(self.client_connection.url_connection)[:1024] + def __enter__(self): self.client_connection.pipeline_client.__enter__() return self @@ -249,19 +231,17 @@ def create_database( # pylint: disable=redefined-builtin Create a new database with the given ID (name). :param id: ID (name) of the database to create. - :param str session_token: Token for use with Session consistency. - :param initial_headers: Initial headers to be sent as part of the request. - :type initial_headers: dict[str, str] - :param access_condition: Conditions Associated with the request. - :type access_condition: dict[str, str] :param bool populate_query_metrics: Enable returning query metrics in response headers. :param int offer_throughput: The provisioned throughput for this offer. - :param request_options: Dictionary of additional properties to be used for the request. - :type request_options: dict[str, Any] - :param Callable response_hook: a callable invoked with the response metadata + :keyword str session_token: Token for use with Session consistency. + :keyword dict[str,str] initial_headers: Initial headers to be sent as part of the request. + :keyword str etag: An ETag value, or the wildcard character (*). Used to check if the resource + has changed, and act according to the condition specified by the `match_condition` parameter. + :keyword ~azure.core.MatchConditions match_condition: The match condition to use upon the etag. + :keyword Callable response_hook: A callable invoked with the response metadata. :returns: A DatabaseProxy instance representing the new database. :rtype: ~azure.cosmos.DatabaseProxy - :raises ~azure.cosmos.errors.CosmosResourceExistsError: Database with the given ID already exists. + :raises ~azure.cosmos.exceptions.CosmosResourceExistsError: Database with the given ID already exists. .. admonition:: Example: @@ -303,19 +283,17 @@ def create_database_if_not_exists( # pylint: disable=redefined-builtin if they differ from what was passed into the method. :param id: ID (name) of the database to read or create. - :param str session_token: Token for use with Session consistency. - :param initial_headers: Initial headers to be sent as part of the request. - :type initial_headers: dict[str, str] - :param access_condition: Conditions Associated with the request. - :type access_condition: dict[str, str] :param bool populate_query_metrics: Enable returning query metrics in response headers. :param int offer_throughput: The provisioned throughput for this offer. - :param request_options: Dictionary of additional properties to be used for the request. - :type request_options: dict[str, Any] - :param Callable response_hook: a callable invoked with the response metadata + :keyword str session_token: Token for use with Session consistency. + :keyword dict[str,str] initial_headers: Initial headers to be sent as part of the request. + :keyword str etag: An ETag value, or the wildcard character (*). Used to check if the resource + has changed, and act according to the condition specified by the `match_condition` parameter. + :keyword ~azure.core.MatchConditions match_condition: The match condition to use upon the etag. + :keyword Callable response_hook: A callable invoked with the response metadata. :returns: A DatabaseProxy instance representing the database. :rtype: ~azure.cosmos.DatabaseProxy - :raises ~azure.cosmos.errors.CosmosHttpResponseError: The database read or creation failed. + :raises ~azure.cosmos.exceptions.CosmosHttpResponseError: The database read or creation failed. """ try: database_proxy = self.get_database_client(id) @@ -334,8 +312,7 @@ def create_database_if_not_exists( # pylint: disable=redefined-builtin def get_database_client(self, database): # type: (Union[str, DatabaseProxy, Dict[str, Any]]) -> DatabaseProxy - """ - Retrieve an existing database with the ID (name) `id`. + """Retrieve an existing database with the ID (name) `id`. :param database: The ID (name), dict representing the properties or `DatabaseProxy` instance of the database to read. @@ -360,17 +337,13 @@ def list_databases( **kwargs # type: Any ): # type: (...) -> Iterable[Dict[str, Any]] - """ - List the databases in a Cosmos DB SQL database account. + """List the databases in a Cosmos DB SQL database account. :param int max_item_count: Max number of items to be returned in the enumeration operation. - :param str session_token: Token for use with Session consistency. - :param initial_headers: Initial headers to be sent as part of the request. - :type initial_headers: dict[str, str] :param bool populate_query_metrics: Enable returning query metrics in response headers. - :param feed_options: Dictionary of additional properties to be used for the request. - :type feed_options: dict[str, str] - :param Callable response_hook: a callable invoked with the response metadata + :keyword str session_token: Token for use with Session consistency. + :keyword dict[str,str] initial_headers: Initial headers to be sent as part of the request. + :keyword Callable response_hook: A callable invoked with the response metadata. :returns: An Iterable of database properties (dicts). :rtype: Iterable[dict[str, str]] """ @@ -397,21 +370,17 @@ def query_databases( **kwargs # type: Any ): # type: (...) -> Iterable[Dict[str, Any]] - """ - Query the databases in a Cosmos DB SQL database account. + """Query the databases in a Cosmos DB SQL database account. :param str query: The Azure Cosmos DB SQL query to execute. :param list[str] parameters: Optional array of parameters to the query. Ignored if no query is provided. :param bool enable_cross_partition_query: Allow scan on the queries which couldn't be served as indexing was opted out on the requested paths. :param int max_item_count: Max number of items to be returned in the enumeration operation. - :param str session_token: Token for use with Session consistency. - :param initial_headers: Initial headers to be sent as part of the request. - :type initial_headers: dict[str, str] :param bool populate_query_metrics: Enable returning query metrics in response headers. - :param feed_options: Dictionary of additional properties to be used for the request. - :type feed_options: dict[str, Any] - :param Callable response_hook: a callable invoked with the response metadata + :keyword str session_token: Token for use with Session consistency. + :keyword dict[str,str] initial_headers: Initial headers to be sent as part of the request. + :keyword Callable response_hook: A callable invoked with the response metadata. :returns: An Iterable of database properties (dicts). :rtype: Iterable[dict[str, str]] """ @@ -446,22 +415,19 @@ def delete_database( **kwargs # type: Any ): # type: (...) -> None - """ - Delete the database with the given ID (name). + """Delete the database with the given ID (name). :param database: The ID (name), dict representing the properties or :class:`DatabaseProxy` instance of the database to delete. :type database: str or dict(str, str) or ~azure.cosmos.DatabaseProxy - :param str session_token: Token for use with Session consistency. - :param initial_headers: Initial headers to be sent as part of the request. - :type initial_headers: dict[str, str] - :param access_condition: Conditions Associated with the request. - :type access_condition: dict[str, str] :param bool populate_query_metrics: Enable returning query metrics in response headers. - :param request_options: Dictionary of additional properties to be used for the request. - :type request_options: dict[str, Any] - :param Callable response_hook: a callable invoked with the response metadata - :raises ~azure.cosmos.errors.CosmosHttpResponseError: If the database couldn't be deleted. + :keyword str session_token: Token for use with Session consistency. + :keyword dict[str,str] initial_headers: Initial headers to be sent as part of the request. + :keyword str etag: An ETag value, or the wildcard character (*). Used to check if the resource + has changed, and act according to the condition specified by the `match_condition` parameter. + :keyword ~azure.core.MatchConditions match_condition: The match condition to use upon the etag. + :keyword Callable response_hook: A callable invoked with the response metadata. + :raises ~azure.cosmos.exceptions.CosmosHttpResponseError: If the database couldn't be deleted. :rtype: None """ request_options = build_options(kwargs) @@ -480,7 +446,7 @@ def get_database_account(self, **kwargs): """ Retrieve the database account information. - :param Callable response_hook: a callable invoked with the response metadata + :keyword Callable response_hook: A callable invoked with the response metadata. :returns: A `DatabaseAccount` instance representing the Cosmos DB Database Account. :rtype: ~azure.cosmos.DatabaseAccount """ diff --git a/sdk/cosmos/azure-cosmos/azure/cosmos/database.py b/sdk/cosmos/azure-cosmos/azure/cosmos/database.py index cc5067d003db..b4196cb1ae6e 100644 --- a/sdk/cosmos/azure-cosmos/azure/cosmos/database.py +++ b/sdk/cosmos/azure-cosmos/azure/cosmos/database.py @@ -32,7 +32,7 @@ from .container import ContainerProxy from .offer import Offer from .http_constants import StatusCodes -from .errors import CosmosResourceNotFoundError +from .exceptions import CosmosResourceNotFoundError from .user import UserProxy __all__ = ("DatabaseProxy",) @@ -76,6 +76,10 @@ def __init__(self, client_connection, id, properties=None): # pylint: disable=r self.database_link = u"dbs/{}".format(self.id) self._properties = properties + def __repr__(self): + # type () -> str + return "".format(self.database_link)[:1024] + @staticmethod def _get_container_id(container_or_id): # type: (Union[str, ContainerProxy, Dict[str, Any]]) -> str @@ -110,18 +114,14 @@ def _get_properties(self): @distributed_trace def read(self, populate_query_metrics=None, **kwargs): # type: (Optional[bool], Any) -> Dict[str, Any] - """ - Read the database properties. + """Read the database properties. - :param database: The ID (name), dict representing the properties or :class:`DatabaseProxy` - instance of the database to read. - :param session_token: Token for use with Session consistency. - :param initial_headers: Initial headers to be sent as part of the request. :param bool populate_query_metrics: Enable returning query metrics in response headers. - :param request_options: Dictionary of additional properties to be used for the request. - :param response_hook: a callable invoked with the response metadata + :keyword str session_token: Token for use with Session consistency. + :keyword dict[str,str] initial_headers: Initial headers to be sent as part of the request. + :keyword Callable response_hook: A callable invoked with the response metadata. :rtype: Dict[Str, Any] - :raises ~azure.cosmos.errors.CosmosHttpResponseError: If the given database couldn't be retrieved. + :raises ~azure.cosmos.exceptions.CosmosHttpResponseError: If the given database couldn't be retrieved. """ # TODO this helper function should be extracted from CosmosClient from .cosmos_client import CosmosClient @@ -155,8 +155,7 @@ def create_container( **kwargs # type: Any ): # type: (...) -> ContainerProxy - """ - Create a new container with the given ID (name). + """Create a new container with the given ID (name). If a container with the given ID already exists, a CosmosResourceExistsError is raised. @@ -164,17 +163,18 @@ def create_container( :param partition_key: The partition key to use for the container. :param indexing_policy: The indexing policy to apply to the container. :param default_ttl: Default time to live (TTL) for items in the container. If unspecified, items do not expire. - :param session_token: Token for use with Session consistency. - :param initial_headers: Initial headers to be sent as part of the request. - :param access_condition: Conditions Associated with the request. :param populate_query_metrics: Enable returning query metrics in response headers. :param offer_throughput: The provisioned throughput for this offer. :param unique_key_policy: The unique key policy to apply to the container. :param conflict_resolution_policy: The conflict resolution policy to apply to the container. - :param request_options: Dictionary of additional properties to be used for the request. - :param response_hook: a callable invoked with the response metadata + :keyword str session_token: Token for use with Session consistency. + :keyword dict[str,str] initial_headers: Initial headers to be sent as part of the request. + :keyword str etag: An ETag value, or the wildcard character (*). Used to check if the resource + has changed, and act according to the condition specified by the `match_condition` parameter. + :keyword ~azure.core.MatchConditions match_condition: The match condition to use upon the etag. + :keyword Callable response_hook: A callable invoked with the response metadata. :returns: A `ContainerProxy` instance representing the new container. - :raises ~azure.cosmos.errors.CosmosHttpResponseError: The container creation failed. + :raises ~azure.cosmos.exceptions.CosmosHttpResponseError: The container creation failed. :rtype: ~azure.cosmos.ContainerProxy .. admonition:: Example: @@ -196,15 +196,15 @@ def create_container( :name: create_container_with_settings """ definition = dict(id=id) # type: Dict[str, Any] - if partition_key: + if partition_key is not None: definition["partitionKey"] = partition_key - if indexing_policy: + if indexing_policy is not None: definition["indexingPolicy"] = indexing_policy - if default_ttl: + if default_ttl is not None: definition["defaultTtl"] = default_ttl - if unique_key_policy: + if unique_key_policy is not None: definition["uniqueKeyPolicy"] = unique_key_policy - if conflict_resolution_policy: + if conflict_resolution_policy is not None: definition["conflictResolutionPolicy"] = conflict_resolution_policy request_options = build_options(kwargs) @@ -237,8 +237,7 @@ def create_container_if_not_exists( **kwargs # type: Any ): # type: (...) -> ContainerProxy - """ - Create the container if it does not exist already. + """Create the container if it does not exist already. If the container already exists, the existing settings are returned. Note: it does not check or update the existing container settings or offer throughput @@ -248,17 +247,18 @@ def create_container_if_not_exists( :param partition_key: The partition key to use for the container. :param indexing_policy: The indexing policy to apply to the container. :param default_ttl: Default time to live (TTL) for items in the container. If unspecified, items do not expire. - :param session_token: Token for use with Session consistency. - :param initial_headers: Initial headers to be sent as part of the request. - :param access_condition: Conditions Associated with the request. :param populate_query_metrics: Enable returning query metrics in response headers. :param offer_throughput: The provisioned throughput for this offer. :param unique_key_policy: The unique key policy to apply to the container. :param conflict_resolution_policy: The conflict resolution policy to apply to the container. - :param request_options: Dictionary of additional properties to be used for the request. - :param response_hook: a callable invoked with the response metadata + :keyword str session_token: Token for use with Session consistency. + :keyword dict[str,str] initial_headers: Initial headers to be sent as part of the request. + :keyword str etag: An ETag value, or the wildcard character (*). Used to check if the resource + has changed, and act according to the condition specified by the `match_condition` parameter. + :keyword ~azure.core.MatchConditions match_condition: The match condition to use upon the etag. + :keyword Callable response_hook: A callable invoked with the response metadata. :returns: A `ContainerProxy` instance representing the container. - :raises ~azure.cosmos.errors.CosmosHttpResponseError: The container read or creation failed. + :raises ~azure.cosmos.exceptions.CosmosHttpResponseError: The container read or creation failed. :rtype: ~azure.cosmos.ContainerProxy """ @@ -289,19 +289,19 @@ def delete_container( **kwargs # type: Any ): # type: (...) -> None - """ - Delete the container + """Delete the container. :param container: The ID (name) of the container to delete. You can either pass in the ID of the container to delete, a :class:`ContainerProxy` instance or a dict representing the properties of the container. - :param session_token: Token for use with Session consistency. - :param initial_headers: Initial headers to be sent as part of the request. - :param access_condition: Conditions Associated with the request. :param populate_query_metrics: Enable returning query metrics in response headers. - :param request_options: Dictionary of additional properties to be used for the request. - :param response_hook: a callable invoked with the response metadata - :raises ~azure.cosmos.errors.CosmosHttpResponseError: If the container couldn't be deleted. + :keyword str session_token: Token for use with Session consistency. + :keyword dict[str,str] initial_headers: Initial headers to be sent as part of the request. + :keyword str etag: An ETag value, or the wildcard character (*). Used to check if the resource + has changed, and act according to the condition specified by the `match_condition` parameter. + :keyword ~azure.core.MatchConditions match_condition: The match condition to use upon the etag. + :keyword Callable response_hook: A callable invoked with the response metadata. + :raises ~azure.cosmos.exceptions.CosmosHttpResponseError: If the container couldn't be deleted. :rtype: None """ request_options = build_options(kwargs) @@ -316,8 +316,7 @@ def delete_container( def get_container_client(self, container): # type: (Union[str, ContainerProxy, Dict[str, Any]]) -> ContainerProxy - """ - Get the specified `ContainerProxy`, or a container with specified ID (name). + """Get the specified `ContainerProxy`, or a container with specified ID (name). :param container: The ID (name) of the container, a :class:`ContainerProxy` instance, or a dict representing the properties of the container to be retrieved. @@ -345,15 +344,13 @@ def get_container_client(self, container): @distributed_trace def list_containers(self, max_item_count=None, populate_query_metrics=None, **kwargs): # type: (Optional[int], Optional[bool], Any) -> Iterable[Dict[str, Any]] - """ - List the containers in the database. + """List the containers in the database. :param max_item_count: Max number of items to be returned in the enumeration operation. - :param session_token: Token for use with Session consistency. - :param initial_headers: Initial headers to be sent as part of the request. :param populate_query_metrics: Enable returning query metrics in response headers. - :param feed_options: Dictionary of additional properties to be used for the request. - :param response_hook: a callable invoked with the response metadata + :keyword str session_token: Token for use with Session consistency. + :keyword dict[str,str] initial_headers: Initial headers to be sent as part of the request. + :keyword Callable response_hook: A callable invoked with the response metadata. :returns: An Iterable of container properties (dicts). :rtype: Iterable[dict[str, Any]] @@ -391,17 +388,15 @@ def query_containers( **kwargs # type: Any ): # type: (...) -> Iterable[Dict[str, Any]] - """ - List properties for containers in the current database. + """List properties for containers in the current database. :param query: The Azure Cosmos DB SQL query to execute. :param parameters: Optional array of parameters to the query. Ignored if no query is provided. :param max_item_count: Max number of items to be returned in the enumeration operation. - :param session_token: Token for use with Session consistency. - :param initial_headers: Initial headers to be sent as part of the request. :param populate_query_metrics: Enable returning query metrics in response headers. - :param feed_options: Dictionary of additional properties to be used for the request. - :param response_hook: a callable invoked with the response metadata + :keyword str session_token: Token for use with Session consistency. + :keyword dict[str,str] initial_headers: Initial headers to be sent as part of the request. + :keyword Callable response_hook: A callable invoked with the response metadata. :returns: An Iterable of container properties (dicts). :rtype: Iterable[dict[str, Any]] """ @@ -434,9 +429,10 @@ def replace_container( **kwargs # type: Any ): # type: (...) -> ContainerProxy - """ - Reset the properties of the container. Property changes are persisted immediately. - Any properties not specified will be reset to their default values. + """Reset the properties of the container. + + Property changes are persisted immediately. Any properties not specified will be reset to + their default values. :param container: The ID (name), dict representing the properties or :class:`ContainerProxy` instance of the container to be replaced. @@ -445,13 +441,14 @@ def replace_container( :param default_ttl: Default time to live (TTL) for items in the container. If unspecified, items do not expire. :param conflict_resolution_policy: The conflict resolution policy to apply to the container. - :param session_token: Token for use with Session consistency. - :param access_condition: Conditions Associated with the request. - :param initial_headers: Initial headers to be sent as part of the request. :param populate_query_metrics: Enable returning query metrics in response headers. - :param request_options: Dictionary of additional properties to be used for the request. - :param response_hook: a callable invoked with the response metadata - :raises ~azure.cosmos.errors.CosmosHttpResponseError: Raised if the container couldn't be replaced. + :keyword str session_token: Token for use with Session consistency. + :keyword str etag: An ETag value, or the wildcard character (*). Used to check if the resource + has changed, and act according to the condition specified by the `match_condition` parameter. + :keyword ~azure.core.MatchConditions match_condition: The match condition to use upon the etag. + :keyword dict[str,str] initial_headers: Initial headers to be sent as part of the request. + :keyword Callable response_hook: A callable invoked with the response metadata. + :raises ~azure.cosmos.exceptions.CosmosHttpResponseError: Raised if the container couldn't be replaced. This includes if the container with given id does not exist. :returns: A `ContainerProxy` instance representing the container after replace completed. :rtype: ~azure.cosmos.ContainerProxy @@ -499,12 +496,10 @@ def replace_container( @distributed_trace def list_users(self, max_item_count=None, **kwargs): # type: (Optional[int], Any) -> Iterable[Dict[str, Any]] - """ - List all users in the container. + """List all users in the container. :param max_item_count: Max number of users to be returned in the enumeration operation. - :param feed_options: Dictionary of additional properties to be used for the request. - :param response_hook: a callable invoked with the response metadata + :keyword Callable response_hook: A callable invoked with the response metadata. :returns: An Iterable of user properties (dicts). :rtype: Iterable[dict[str, Any]] """ @@ -523,14 +518,12 @@ def list_users(self, max_item_count=None, **kwargs): @distributed_trace def query_users(self, query, parameters=None, max_item_count=None, **kwargs): # type: (str, Optional[List[str]], Optional[int], Any) -> Iterable[Dict[str, Any]] - """ - Return all users matching the given `query`. + """Return all users matching the given `query`. :param query: The Azure Cosmos DB SQL query to execute. :param parameters: Optional array of parameters to the query. Ignored if no query is provided. :param max_item_count: Max number of users to be returned in the enumeration operation. - :param feed_options: Dictionary of additional properties to be used for the request. - :param response_hook: a callable invoked with the response metadata + :keyword Callable response_hook: A callable invoked with the response metadata. :returns: An Iterable of user properties (dicts). :rtype: Iterable[str, Any] """ @@ -551,13 +544,12 @@ def query_users(self, query, parameters=None, max_item_count=None, **kwargs): def get_user_client(self, user): # type: (Union[str, UserProxy, Dict[str, Any]]) -> UserProxy - """ - Get the user identified by `user`. + """Get the user identified by `user`. :param user: The ID (name), dict representing the properties or :class:`UserProxy` instance of the user to be retrieved. :returns: A `UserProxy` instance representing the retrieved user. - :raises ~azure.cosmos.errors.CosmosHttpResponseError: If the given user couldn't be retrieved. + :raises ~azure.cosmos.exceptions.CosmosHttpResponseError: If the given user couldn't be retrieved. :rtype: ~azure.cosmos.UserProxy """ if isinstance(user, UserProxy): @@ -572,16 +564,15 @@ def get_user_client(self, user): @distributed_trace def create_user(self, body, **kwargs): # type: (Dict[str, Any], Any) -> UserProxy - """ - Create a user in the container. + """Create a user in the container. + To update or replace an existing user, use the :func:`ContainerProxy.upsert_user` method. :param body: A dict-like object with an `id` key and value representing the user to be created. The user ID must be unique within the database, and consist of no more than 255 characters. - :param request_options: Dictionary of additional properties to be used for the request. - :param response_hook: a callable invoked with the response metadata + :keyword Callable response_hook: A callable invoked with the response metadata. :returns: A `UserProxy` instance representing the new user. - :raises ~azure.cosmos.errors.CosmosHttpResponseError: If the given user couldn't be created. + :raises ~azure.cosmos.exceptions.CosmosHttpResponseError: If the given user couldn't be created. :rtype: ~azure.cosmos.UserProxy .. admonition:: Example: @@ -610,15 +601,14 @@ def create_user(self, body, **kwargs): @distributed_trace def upsert_user(self, body, **kwargs): # type: (Dict[str, Any], Any) -> UserProxy - """ - Insert or update the specified user. + """Insert or update the specified user. + If the user already exists in the container, it is replaced. If it does not, it is inserted. :param body: A dict-like object representing the user to update or insert. - :param request_options: Dictionary of additional properties to be used for the request. - :param response_hook: a callable invoked with the response metadata + :keyword Callable response_hook: A callable invoked with the response metadata. :returns: A `UserProxy` instance representing the upserted user. - :raises ~azure.cosmos.errors.CosmosHttpResponseError: If the given user could not be upserted. + :raises ~azure.cosmos.exceptions.CosmosHttpResponseError: If the given user could not be upserted. :rtype: ~azure.cosmos.UserProxy """ request_options = build_options(kwargs) @@ -643,16 +633,14 @@ def replace_user( **kwargs # type: Any ): # type: (...) -> UserProxy - """ - Replaces the specified user if it exists in the container. + """Replaces the specified user if it exists in the container. :param user: The ID (name), dict representing the properties or :class:`UserProxy` instance of the user to be replaced. :param body: A dict-like object representing the user to replace. - :param request_options: Dictionary of additional properties to be used for the request. - :param response_hook: a callable invoked with the response metadata + :keyword Callable response_hook: A callable invoked with the response metadata. :returns: A `UserProxy` instance representing the user after replace went through. - :raises ~azure.cosmos.errors.CosmosHttpResponseError: If the replace failed or the user with given + :raises ~azure.cosmos.exceptions.CosmosHttpResponseError: If the replace failed or the user with given id does not exist. :rtype: ~azure.cosmos.UserProxy """ @@ -676,15 +664,13 @@ def replace_user( @distributed_trace def delete_user(self, user, **kwargs): # type: (Union[str, UserProxy, Dict[str, Any]], Any) -> None - """ - Delete the specified user from the container. + """Delete the specified user from the container. :param user: The ID (name), dict representing the properties or :class:`UserProxy` instance of the user to be deleted. - :param request_options: Dictionary of additional properties to be used for the request. - :param response_hook: a callable invoked with the response metadata - :raises ~azure.cosmos.errors.CosmosHttpResponseError: The user wasn't deleted successfully. - :raises ~azure.cosmos.errors.CosmosResourceNotFoundError: The user does not exist in the container. + :keyword Callable response_hook: A callable invoked with the response metadata. + :raises ~azure.cosmos.exceptions.CosmosHttpResponseError: The user wasn't deleted successfully. + :raises ~azure.cosmos.exceptions.CosmosResourceNotFoundError: The user does not exist in the container. :rtype: None """ request_options = build_options(kwargs) @@ -699,12 +685,11 @@ def delete_user(self, user, **kwargs): @distributed_trace def read_offer(self, **kwargs): # type: (Any) -> Offer - """ - Read the Offer object for this database. + """Read the Offer object for this database. - :param response_hook: a callable invoked with the response metadata + :keyword Callable response_hook: A callable invoked with the response metadata. :returns: Offer for the database. - :raises ~azure.cosmos.errors.CosmosHttpResponseError: If no offer exists for the database or if the + :raises ~azure.cosmos.exceptions.CosmosHttpResponseError: If no offer exists for the database or if the offer could not be retrieved. :rtype: ~azure.cosmos.Offer """ @@ -729,13 +714,12 @@ def read_offer(self, **kwargs): @distributed_trace def replace_throughput(self, throughput, **kwargs): # type: (Optional[int], Any) -> Offer - """ - Replace the database level throughput. + """Replace the database level throughput. :param throughput: The throughput to be set (an integer). - :param response_hook: a callable invoked with the response metadata + :keyword Callable response_hook: A callable invoked with the response metadata. :returns: Offer for the database, updated with new throughput. - :raises ~azure.cosmos.errors.CosmosHttpResponseError: If no offer exists for the database or if the + :raises ~azure.cosmos.exceptions.CosmosHttpResponseError: If no offer exists for the database or if the offer could not be updated. :rtype: ~azure.cosmos.Offer """ diff --git a/sdk/cosmos/azure-cosmos/azure/cosmos/documents.py b/sdk/cosmos/azure-cosmos/azure/cosmos/documents.py index 830a3147bb39..0f5f4d1e22b6 100644 --- a/sdk/cosmos/azure-cosmos/azure/cosmos/documents.py +++ b/sdk/cosmos/azure-cosmos/azure/cosmos/documents.py @@ -401,7 +401,6 @@ def __init__(self): class _OperationType(object): """Represents the type of the operation """ - Create = "Create" Delete = "Delete" ExecuteJavaScript = "ExecuteJavaScript" @@ -413,6 +412,7 @@ class _OperationType(object): Recreate = "Recreate" Replace = "Replace" SqlQuery = "SqlQuery" + QueryPlan = "QueryPlan" Update = "Update" Upsert = "Upsert" @@ -438,3 +438,32 @@ def IsReadOnlyOperation(operationType): _OperationType.Query, _OperationType.SqlQuery, ) + + @staticmethod + def IsFeedOperation(operationType): + return operationType in ( + _OperationType.Create, + _OperationType.Upsert, + _OperationType.ReadFeed, + _OperationType.Query, + _OperationType.SqlQuery, + _OperationType.QueryPlan, + _OperationType.HeadFeed, + ) + +class _QueryFeature(object): + NoneQuery = "NoneQuery" + Aggregate = "Aggregate" + CompositeAggregate = "CompositeAggregate" + Distinct = "Distinct" + GroupBy = "GroupBy" + MultipleAggregates = "MultipleAggregates" + MultipleOrderBy = "MultipleOrderBy" + OffsetAndLimit = "OffsetAndLimit" + OrderBy = "OrderBy" + Top = "Top" + +class _DistinctType(object): + NoneType = "None" + Ordered = "Ordered" + Unordered = "Unordered" diff --git a/sdk/cosmos/azure-cosmos/azure/cosmos/errors.py b/sdk/cosmos/azure-cosmos/azure/cosmos/errors.py index 698924ef3013..4898d67543e3 100644 --- a/sdk/cosmos/azure-cosmos/azure/cosmos/errors.py +++ b/sdk/cosmos/azure-cosmos/azure/cosmos/errors.py @@ -19,57 +19,13 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. -"""PyCosmos Exceptions in the Azure Cosmos database service. +"""PyCosmos Exceptions in the Azure Cosmos database service. (Deprecated module) """ -from azure.core.exceptions import ( # type: ignore # pylint: disable=unused-import - AzureError, - HttpResponseError, - ResourceExistsError, - ResourceNotFoundError -) -from . import http_constants - - -class CosmosHttpResponseError(HttpResponseError): - """Raised when a HTTP request to the Azure Cosmos has failed.""" - - def __init__(self, status_code=None, message=None, response=None, **kwargs): - """ - :param int status_code: HTTP response code. - :param str message: Error message. - """ - self.headers = response.headers if response else {} - self.sub_status = None - self.http_error_message = message - status = status_code or (int(response.status_code) if response else 0) - - if http_constants.HttpHeaders.SubStatus in self.headers: - self.sub_status = int(self.headers[http_constants.HttpHeaders.SubStatus]) - formatted_message = "Status code: %d Sub-status: %d\n%s" % (status, self.sub_status, str(message)) - else: - formatted_message = "Status code: %d\n%s" % (status, str(message)) - - super(CosmosHttpResponseError, self).__init__(message=formatted_message, response=response, **kwargs) - self.status_code = status - +import warnings -class CosmosResourceNotFoundError(ResourceNotFoundError, CosmosHttpResponseError): - """An error response with status code 404.""" +from .exceptions import * # pylint: disable=wildcard-import, unused-wildcard-import - -class CosmosResourceExistsError(ResourceExistsError, CosmosHttpResponseError): - """An error response with status code 409.""" - - -class CosmosAccessConditionFailedError(CosmosHttpResponseError): - """An error response with status code 412.""" - - -class CosmosClientTimeoutError(AzureError): - """An operation failed to complete within the specified timeout.""" - - def __init__(self, **kwargs): - message = "Client operation failed to complete within specified timeout." - self.response = None - self.history = None - super(CosmosClientTimeoutError, self).__init__(message, **kwargs) +warnings.warn( + "azure.cosmos.errors module is deprecated, use azure.cosmos.exceptions instead", + DeprecationWarning +) diff --git a/sdk/cosmos/azure-cosmos/azure/cosmos/exceptions.py b/sdk/cosmos/azure-cosmos/azure/cosmos/exceptions.py new file mode 100644 index 000000000000..698924ef3013 --- /dev/null +++ b/sdk/cosmos/azure-cosmos/azure/cosmos/exceptions.py @@ -0,0 +1,75 @@ +# The MIT License (MIT) +# Copyright (c) 2014 Microsoft Corporation + +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: + +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. + +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +"""PyCosmos Exceptions in the Azure Cosmos database service. +""" +from azure.core.exceptions import ( # type: ignore # pylint: disable=unused-import + AzureError, + HttpResponseError, + ResourceExistsError, + ResourceNotFoundError +) +from . import http_constants + + +class CosmosHttpResponseError(HttpResponseError): + """Raised when a HTTP request to the Azure Cosmos has failed.""" + + def __init__(self, status_code=None, message=None, response=None, **kwargs): + """ + :param int status_code: HTTP response code. + :param str message: Error message. + """ + self.headers = response.headers if response else {} + self.sub_status = None + self.http_error_message = message + status = status_code or (int(response.status_code) if response else 0) + + if http_constants.HttpHeaders.SubStatus in self.headers: + self.sub_status = int(self.headers[http_constants.HttpHeaders.SubStatus]) + formatted_message = "Status code: %d Sub-status: %d\n%s" % (status, self.sub_status, str(message)) + else: + formatted_message = "Status code: %d\n%s" % (status, str(message)) + + super(CosmosHttpResponseError, self).__init__(message=formatted_message, response=response, **kwargs) + self.status_code = status + + +class CosmosResourceNotFoundError(ResourceNotFoundError, CosmosHttpResponseError): + """An error response with status code 404.""" + + +class CosmosResourceExistsError(ResourceExistsError, CosmosHttpResponseError): + """An error response with status code 409.""" + + +class CosmosAccessConditionFailedError(CosmosHttpResponseError): + """An error response with status code 412.""" + + +class CosmosClientTimeoutError(AzureError): + """An operation failed to complete within the specified timeout.""" + + def __init__(self, **kwargs): + message = "Client operation failed to complete within specified timeout." + self.response = None + self.history = None + super(CosmosClientTimeoutError, self).__init__(message, **kwargs) diff --git a/sdk/cosmos/azure-cosmos/azure/cosmos/http_constants.py b/sdk/cosmos/azure-cosmos/azure/cosmos/http_constants.py index da327a708548..040d6d0c9ab8 100644 --- a/sdk/cosmos/azure-cosmos/azure/cosmos/http_constants.py +++ b/sdk/cosmos/azure-cosmos/azure/cosmos/http_constants.py @@ -89,6 +89,9 @@ class HttpHeaders(object): # Query Query = "x-ms-documentdb-query" IsQuery = "x-ms-documentdb-isquery" + IsQueryPlanRequest = "x-ms-cosmos-is-query-plan-request" + SupportedQueryFeatures = "x-ms-cosmos-supported-query-features" + QueryVersion = "x-ms-cosmos-query-version" # Our custom DocDB headers Continuation = "x-ms-continuation" @@ -262,9 +265,9 @@ class CookieHeaders(object): class Versions(object): """Constants of versions. """ - CurrentVersion = "2018-12-31" SDKName = "azure-cosmos" + QueryVersion = "1.0" class Delimiters(object): diff --git a/sdk/cosmos/azure-cosmos/azure/cosmos/partition_key.py b/sdk/cosmos/azure-cosmos/azure/cosmos/partition_key.py index 309e27ba64de..e361ee45b7bc 100644 --- a/sdk/cosmos/azure-cosmos/azure/cosmos/partition_key.py +++ b/sdk/cosmos/azure-cosmos/azure/cosmos/partition_key.py @@ -54,6 +54,10 @@ def __init__(self, path, kind="Hash", version=2): # pylint: disable=super-init- self.kind = kind self.version = version + def __repr__(self): + # type () -> str + return "".format(self.path)[:1024] + @property def kind(self): return self["kind"] diff --git a/sdk/cosmos/azure-cosmos/azure/cosmos/scripts.py b/sdk/cosmos/azure-cosmos/azure/cosmos/scripts.py index 8cb9e95d3b82..c570661d9f7d 100644 --- a/sdk/cosmos/azure-cosmos/azure/cosmos/scripts.py +++ b/sdk/cosmos/azure-cosmos/azure/cosmos/scripts.py @@ -60,11 +60,9 @@ def _get_resource_link(self, script_or_id, typ): def list_stored_procedures(self, max_item_count=None, **kwargs): # type: (Optional[int], Any) -> Iterable[Dict[str, Any]] - """ - List all stored procedures in the container. + """List all stored procedures in the container. :param int max_item_count: Max number of items to be returned in the enumeration operation. - :param feed_options: Dictionary of additional properties to be used for the request. :returns: An Iterable of stored procedures (dicts). :rtype: Iterable[dict[str, Any]] """ @@ -78,13 +76,11 @@ def list_stored_procedures(self, max_item_count=None, **kwargs): def query_stored_procedures(self, query, parameters=None, max_item_count=None, **kwargs): # type: (str, Optional[List[str]], Optional[int], Any) -> Iterable[Dict[str, Any]] - """ - Return all stored procedures matching the given `query`. + """Return all stored procedures matching the given `query`. :param query: The Azure Cosmos DB SQL query to execute. :param parameters: Optional array of parameters to the query. Ignored if no query is provided. :param max_item_count: Max number of items to be returned in the enumeration operation. - :param feed_options: Dictionary of additional properties to be used for the request. :returns: An Iterable of stored procedures (dicts). :rtype: Iterable[dict[str, Any]] """ @@ -101,13 +97,11 @@ def query_stored_procedures(self, query, parameters=None, max_item_count=None, * def get_stored_procedure(self, sproc, **kwargs): # type: (Union[str, Dict[str, Any]], Any) -> Dict[str, Any] - """ - Get the stored procedure identified by `id`. + """Get the stored procedure identified by `id`. :param sproc: The ID (name) or dict representing stored procedure to retrieve. - :param request_options: Dictionary of additional properties to be used for the request. :returns: A dict representing the retrieved stored procedure. - :raises ~azure.cosmos.errors.CosmosHttpResponseError: If the given stored procedure couldn't be retrieved. + :raises ~azure.cosmos.exceptions.CosmosHttpResponseError: If the given stored procedure couldn't be retrieved. :rtype: dict[str, Any] """ request_options = build_options(kwargs) @@ -118,14 +112,13 @@ def get_stored_procedure(self, sproc, **kwargs): def create_stored_procedure(self, body, **kwargs): # type: (Dict[str, Any], Any) -> Dict[str, Any] - """ - Create a stored procedure in the container. + """Create a stored procedure in the container. + To replace an existing sproc, use the :func:`Container.scripts.replace_stored_procedure` method. :param body: A dict-like object representing the sproc to create. - :param request_options: Dictionary of additional properties to be used for the request. :returns: A dict representing the new stored procedure. - :raises ~azure.cosmos.errors.CosmosHttpResponseError: If the given stored procedure couldn't be created. + :raises ~azure.cosmos.exceptions.CosmosHttpResponseError: If the given stored procedure couldn't be created. :rtype: dict[str, Any] """ request_options = build_options(kwargs) @@ -136,14 +129,12 @@ def create_stored_procedure(self, body, **kwargs): def replace_stored_procedure(self, sproc, body, **kwargs): # type: (Union[str, Dict[str, Any]], Dict[str, Any], Any) -> Dict[str, Any] - """ - Replaces the specified stored procedure if it exists in the container. + """Replaces the specified stored procedure if it exists in the container. :param sproc: The ID (name) or dict representing stored procedure to be replaced. :param body: A dict-like object representing the sproc to replace. - :param request_options: Dictionary of additional properties to be used for the request. :returns: A dict representing the stored procedure after replace went through. - :raises ~azure.cosmos.errors.CosmosHttpResponseError: If the replace failed or the stored + :raises ~azure.cosmos.exceptions.CosmosHttpResponseError: If the replace failed or the stored procedure with given id does not exist. :rtype: dict[str, Any] """ @@ -158,13 +149,11 @@ def replace_stored_procedure(self, sproc, body, **kwargs): def delete_stored_procedure(self, sproc, **kwargs): # type: (Union[str, Dict[str, Any]], Any) -> None - """ - Delete the specified stored procedure from the container. + """Delete the specified stored procedure from the container. :param sproc: The ID (name) or dict representing stored procedure to be deleted. - :param request_options: Dictionary of additional properties to be used for the request. - :raises ~azure.cosmos.errors.CosmosHttpResponseError: The sproc wasn't deleted successfully. - :raises ~azure.cosmos.errors.CosmosResourceNotFoundError: The sproc does not exist in the container. + :raises ~azure.cosmos.exceptions.CosmosHttpResponseError: The sproc wasn't deleted successfully. + :raises ~azure.cosmos.exceptions.CosmosResourceNotFoundError: The sproc does not exist in the container. :rtype: None """ request_options = build_options(kwargs) @@ -182,16 +171,14 @@ def execute_stored_procedure( **kwargs # type: Any ): # type: (...) -> Any - """ - Execute the specified stored procedure. + """Execute the specified stored procedure. :param sproc: The ID (name) or dict representing stored procedure to be executed. + :param partition_key: Specifies the partition key to indicate which partition the sproc should execute on. :param params: List of parameters to be passed to the stored procedure to be executed. :param bool enable_script_logging: Enables or disables script logging for the current request. - :param partition_key: Specifies the partition key to indicate which partition the sproc should execute on. - :param request_options: Dictionary of additional properties to be used for the request. :returns: Result of the executed stored procedure for the given parameters. - :raises ~azure.cosmos.errors.CosmosHttpResponseError: If the stored procedure execution failed + :raises ~azure.cosmos.exceptions.CosmosHttpResponseError: If the stored procedure execution failed or if the stored procedure with given id does not exists in the container. :rtype: dict[str, Any] """ @@ -215,11 +202,9 @@ def execute_stored_procedure( def list_triggers(self, max_item_count=None, **kwargs): # type: (Optional[int], Any) -> Iterable[Dict[str, Any]] - """ - List all triggers in the container. + """List all triggers in the container. :param max_item_count: Max number of items to be returned in the enumeration operation. - :param feed_options: Dictionary of additional properties to be used for the request. :returns: An Iterable of triggers (dicts). :rtype: Iterable[dict[str, Any]] """ @@ -233,13 +218,11 @@ def list_triggers(self, max_item_count=None, **kwargs): def query_triggers(self, query, parameters=None, max_item_count=None, **kwargs): # type: (str, Optional[List[str]], Optional[int], Any) -> Iterable[Dict[str, Any]] - """ - Return all triggers matching the given `query`. + """Return all triggers matching the given `query`. :param query: The Azure Cosmos DB SQL query to execute. :param parameters: Optional array of parameters to the query. Ignored if no query is provided. :param max_item_count: Max number of items to be returned in the enumeration operation. - :param feed_options: Dictionary of additional properties to be used for the request. :returns: An Iterable of triggers (dicts). :rtype: Iterable[dict[str, Any]] """ @@ -256,13 +239,11 @@ def query_triggers(self, query, parameters=None, max_item_count=None, **kwargs): def get_trigger(self, trigger, **kwargs): # type: (Union[str, Dict[str, Any]], Any) -> Dict[str, Any] - """ - Get the trigger identified by `id`. + """Get the trigger identified by `id`. :param trigger: The ID (name) or dict representing trigger to retrieve. - :param request_options: Dictionary of additional properties to be used for the request. :returns: A dict representing the retrieved trigger. - :raises ~azure.cosmos.errors.CosmosHttpResponseError: If the given trigger couldn't be retrieved. + :raises ~azure.cosmos.exceptions.CosmosHttpResponseError: If the given trigger couldn't be retrieved. :rtype: dict[str, Any] """ request_options = build_options(kwargs) @@ -273,14 +254,13 @@ def get_trigger(self, trigger, **kwargs): def create_trigger(self, body, **kwargs): # type: (Dict[str, Any], Any) -> Dict[str, Any] - """ - Create a trigger in the container. + """Create a trigger in the container. + To replace an existing trigger, use the :func:`ContainerProxy.scripts.replace_trigger` method. :param body: A dict-like object representing the trigger to create. - :param request_options: Dictionary of additional properties to be used for the request. :returns: A dict representing the new trigger. - :raises ~azure.cosmos.errors.CosmosHttpResponseError: If the given trigger couldn't be created. + :raises ~azure.cosmos.exceptions.CosmosHttpResponseError: If the given trigger couldn't be created. :rtype: dict[str, Any] """ request_options = build_options(kwargs) @@ -291,14 +271,12 @@ def create_trigger(self, body, **kwargs): def replace_trigger(self, trigger, body, **kwargs): # type: (Union[str, Dict[str, Any]], Dict[str, Any], Any) -> Dict[str, Any] - """ - Replaces the specified tigger if it exists in the container. + """Replaces the specified tigger if it exists in the container. :param trigger: The ID (name) or dict representing trigger to be replaced. :param body: A dict-like object representing the trigger to replace. - :param request_options: Dictionary of additional properties to be used for the request. :returns: A dict representing the trigger after replace went through. - :raises ~azure.cosmos.errors.CosmosHttpResponseError: If the replace failed or the trigger with given + :raises ~azure.cosmos.exceptions.CosmosHttpResponseError: If the replace failed or the trigger with given id does not exist. :rtype: dict[str, Any] """ @@ -313,13 +291,11 @@ def replace_trigger(self, trigger, body, **kwargs): def delete_trigger(self, trigger, **kwargs): # type: (Union[str, Dict[str, Any]], Any) -> None - """ - Delete the specified trigger from the container. + """Delete the specified trigger from the container. :param trigger: The ID (name) or dict representing trigger to be deleted. - :param request_options: Dictionary of additional properties to be used for the request. - :raises ~azure.cosmos.errors.CosmosHttpResponseError: The trigger wasn't deleted successfully. - :raises ~azure.cosmos.errors.CosmosResourceNotFoundError: The trigger does not exist in the container. + :raises ~azure.cosmos.exceptions.CosmosHttpResponseError: The trigger wasn't deleted successfully. + :raises ~azure.cosmos.exceptions.CosmosResourceNotFoundError: The trigger does not exist in the container. :rtype: None """ request_options = build_options(kwargs) @@ -330,11 +306,9 @@ def delete_trigger(self, trigger, **kwargs): def list_user_defined_functions(self, max_item_count=None, **kwargs): # type: (Optional[int], Any) -> Iterable[Dict[str, Any]] - """ - List all user defined functions in the container. + """List all user defined functions in the container. :param max_item_count: Max number of items to be returned in the enumeration operation. - :param feed_options: Dictionary of additional properties to be used for the request. :returns: An Iterable of user defined functions (dicts). :rtype: Iterable[dict[str, Any]] """ @@ -348,13 +322,11 @@ def list_user_defined_functions(self, max_item_count=None, **kwargs): def query_user_defined_functions(self, query, parameters=None, max_item_count=None, **kwargs): # type: (str, Optional[List[str]], Optional[int], Any) -> Iterable[Dict[str, Any]] - """ - Return all user defined functions matching the given `query`. + """Return all user defined functions matching the given `query`. :param query: The Azure Cosmos DB SQL query to execute. :param parameters: Optional array of parameters to the query. Ignored if no query is provided. :param max_item_count: Max number of items to be returned in the enumeration operation. - :param feed_options: Dictionary of additional properties to be used for the request. :returns: An Iterable of user defined functions (dicts). :rtype: Iterable[dict[str, Any]] """ @@ -371,13 +343,11 @@ def query_user_defined_functions(self, query, parameters=None, max_item_count=No def get_user_defined_function(self, udf, **kwargs): # type: (Union[str, Dict[str, Any]], Any) -> Dict[str, Any] - """ - Get the stored procedure identified by `id`. + """Get the stored procedure identified by `id`. :param udf: The ID (name) or dict representing udf to retrieve. - :param request_options: Dictionary of additional properties to be used for the request. :returns: A dict representing the retrieved user defined function. - :raises ~azure.cosmos.errors.CosmosHttpResponseError: If the given user defined function couldn't be retrieved. + :raises ~azure.cosmos.exceptions.CosmosHttpResponseError: If the user defined function couldn't be retrieved. :rtype: Iterable[dict[str, Any]] """ request_options = build_options(kwargs) @@ -388,14 +358,13 @@ def get_user_defined_function(self, udf, **kwargs): def create_user_defined_function(self, body, **kwargs): # type: (Dict[str, Any], Any) -> Dict[str, Any] - """ - Create a user defined function in the container. + """Create a user defined function in the container. + To replace an existing udf, use the :func:`ContainerProxy.scripts.replace_user_defined_function` method. :param body: A dict-like object representing the udf to create. - :param request_options: Dictionary of additional properties to be used for the request. :returns: A dict representing the new user defined function. - :raises ~azure.cosmos.errors.CosmosHttpResponseError: If the given user defined function couldn't be created. + :raises ~azure.cosmos.exceptions.CosmosHttpResponseError: If the user defined function couldn't be created. :rtype: dict[str, Any] """ request_options = build_options(kwargs) @@ -406,15 +375,13 @@ def create_user_defined_function(self, body, **kwargs): def replace_user_defined_function(self, udf, body, **kwargs): # type: (Union[str, Dict[str, Any]], Dict[str, Any], Any) -> Dict[str, Any] - """ - Replaces the specified user defined function if it exists in the container. + """Replaces the specified user defined function if it exists in the container. :param udf: The ID (name) or dict representing udf to be replaced. :param body: A dict-like object representing the udf to replace. - :param request_options: Dictionary of additional properties to be used for the request. :returns: A dict representing the user defined function after replace went through. - :raises ~azure.cosmos.errors.CosmosHttpResponseError: If the replace failed or the user defined function with - given id does not exist. + :raises ~azure.cosmos.exceptions.CosmosHttpResponseError: If the replace failed or the user defined function + with the given id does not exist. :rtype: dict[str, Any] """ request_options = build_options(kwargs) @@ -428,13 +395,11 @@ def replace_user_defined_function(self, udf, body, **kwargs): def delete_user_defined_function(self, udf, **kwargs): # type: (Union[str, Dict[str, Any]], Any) -> None - """ - Delete the specified user defined function from the container. + """Delete the specified user defined function from the container. :param udf: The ID (name) or dict representing udf to be deleted. - :param request_options: Dictionary of additional properties to be used for the request. - :raises ~azure.cosmos.errors.CosmosHttpResponseError: The udf wasn't deleted successfully. - :raises ~azure.cosmos.errors.CosmosResourceNotFoundError: The UDF does not exist in the container. + :raises ~azure.cosmos.exceptions.CosmosHttpResponseError: The udf wasn't deleted successfully. + :raises ~azure.cosmos.exceptions.CosmosResourceNotFoundError: The UDF does not exist in the container. :rtype: None """ request_options = build_options(kwargs) diff --git a/sdk/cosmos/azure-cosmos/azure/cosmos/user.py b/sdk/cosmos/azure-cosmos/azure/cosmos/user.py index 9352d6755582..fe3221e7fb73 100644 --- a/sdk/cosmos/azure-cosmos/azure/cosmos/user.py +++ b/sdk/cosmos/azure-cosmos/azure/cosmos/user.py @@ -47,6 +47,10 @@ def __init__(self, client_connection, id, database_link, properties=None): # py self.user_link = u"{}/users/{}".format(database_link, id) self._properties = properties + def __repr__(self): + # type () -> str + return "".format(self.user_link)[:1024] + def _get_permission_link(self, permission_or_id): # type: (Union[Permission, str, Dict[str, Any]]) -> str if isinstance(permission_or_id, six.string_types): @@ -66,13 +70,11 @@ def _get_properties(self): @distributed_trace def read(self, **kwargs): # type: (Any) -> Dict[str, Any] - """ - Read user propertes. + """Read user propertes. - :param request_options: Dictionary of additional properties to be used for the request. - :param response_hook: a callable invoked with the response metadata - :returns: A :class:`UserProxy` instance representing the retrieved user. - :raises ~azure.cosmos.errors.CosmosHttpResponseError: If the given user couldn't be retrieved. + :keyword Callable response_hook: A callable invoked with the response metadata. + :returns: A dictionary of the retrieved user properties. + :raises ~azure.cosmos.exceptions.CosmosHttpResponseError: If the given user couldn't be retrieved. :rtype: dict[str, Any] """ request_options = build_options(kwargs) @@ -88,12 +90,10 @@ def read(self, **kwargs): @distributed_trace def list_permissions(self, max_item_count=None, **kwargs): # type: (Optional[int], Any) -> Iterable[Dict[str, Any]] - """ - List all permission for the user. + """List all permission for the user. :param max_item_count: Max number of permissions to be returned in the enumeration operation. - :param feed_options: Dictionary of additional properties to be used for the request. - :param response_hook: a callable invoked with the response metadata + :keyword Callable response_hook: A callable invoked with the response metadata. :returns: An Iterable of permissions (dicts). :rtype: Iterable[dict[str, Any]] """ @@ -118,14 +118,12 @@ def query_permissions( **kwargs ): # type: (str, Optional[List[str]], Optional[int], Any) -> Iterable[Dict[str, Any]] - """ - Return all permissions matching the given `query`. + """Return all permissions matching the given `query`. :param query: The Azure Cosmos DB SQL query to execute. :param parameters: Optional array of parameters to the query. Ignored if no query is provided. :param max_item_count: Max number of permissions to be returned in the enumeration operation. - :param feed_options: Dictionary of additional properties to be used for the request. - :param response_hook: a callable invoked with the response metadata + :keyword Callable response_hook: A callable invoked with the response metadata. :returns: An Iterable of permissions (dicts). :rtype: Iterable[dict[str, Any]] """ @@ -149,15 +147,13 @@ def query_permissions( @distributed_trace def get_permission(self, permission, **kwargs): # type: (str, Any) -> Permission - """ - Get the permission identified by `id`. + """Get the permission identified by `id`. :param permission: The ID (name), dict representing the properties or :class:`Permission` instance of the permission to be retrieved. - :param request_options: Dictionary of additional properties to be used for the request. - :param response_hook: a callable invoked with the response metadata + :keyword Callable response_hook: A callable invoked with the response metadata. :returns: A dict representing the retrieved permission. - :raises ~azure.cosmos.errors.CosmosHttpResponseError: If the given permission couldn't be retrieved. + :raises ~azure.cosmos.exceptions.CosmosHttpResponseError: If the given permission couldn't be retrieved. :rtype: dict[str, Any] """ request_options = build_options(kwargs) @@ -181,15 +177,14 @@ def get_permission(self, permission, **kwargs): @distributed_trace def create_permission(self, body, **kwargs): # type: (Dict[str, Any], Any) -> Permission - """ - Create a permission for the user. + """Create a permission for the user. + To update or replace an existing permision, use the :func:`UserProxy.upsert_permission` method. :param body: A dict-like object representing the permission to create. - :param request_options: Dictionary of additional properties to be used for the request. - :param response_hook: a callable invoked with the response metadata + :keyword Callable response_hook: A callable invoked with the response metadata. :returns: A dict representing the new permission. - :raises ~azure.cosmos.errors.CosmosHttpResponseError: If the given permission couldn't be created. + :raises ~azure.cosmos.exceptions.CosmosHttpResponseError: If the given permission couldn't be created. :rtype: dict[str, Any] """ request_options = build_options(kwargs) @@ -213,15 +208,14 @@ def create_permission(self, body, **kwargs): @distributed_trace def upsert_permission(self, body, **kwargs): # type: (Dict[str, Any], Any) -> Permission - """ - Insert or update the specified permission. + """Insert or update the specified permission. + If the permission already exists in the container, it is replaced. If it does not, it is inserted. :param body: A dict-like object representing the permission to update or insert. - :param request_options: Dictionary of additional properties to be used for the request. - :param response_hook: a callable invoked with the response metadata + :param Callable response_hook: A callable invoked with the response metadata. :returns: A dict representing the upserted permission. - :raises ~azure.cosmos.errors.CosmosHttpResponseError: If the given permission could not be upserted. + :raises ~azure.cosmos.exceptions.CosmosHttpResponseError: If the given permission could not be upserted. :rtype: dict[str, Any] """ request_options = build_options(kwargs) @@ -245,16 +239,14 @@ def upsert_permission(self, body, **kwargs): @distributed_trace def replace_permission(self, permission, body, **kwargs): # type: (str, Dict[str, Any], Any) -> Permission - """ - Replaces the specified permission if it exists for the user. + """Replaces the specified permission if it exists for the user. :param permission: The ID (name), dict representing the properties or :class:`Permission` instance of the permission to be replaced. :param body: A dict-like object representing the permission to replace. - :param request_options: Dictionary of additional properties to be used for the request. - :param response_hook: a callable invoked with the response metadata + :keyword Callable response_hook: A callable invoked with the response metadata. :returns: A dict representing the permission after replace went through. - :raises ~azure.cosmos.errors.CosmosHttpResponseError: If the replace failed or the permission + :raises ~azure.cosmos.exceptions.CosmosHttpResponseError: If the replace failed or the permission with given id does not exist. :rtype: dict[str, Any] """ @@ -279,15 +271,13 @@ def replace_permission(self, permission, body, **kwargs): @distributed_trace def delete_permission(self, permission, **kwargs): # type: (str, Any) -> None - """ - Delete the specified permission from the user. + """Delete the specified permission from the user. :param permission: The ID (name), dict representing the properties or :class:`Permission` instance of the permission to be replaced. - :param request_options: Dictionary of additional properties to be used for the request. - :param response_hook: a callable invoked with the response metadata - :raises ~azure.cosmos.errors.CosmosHttpResponseError: The permission wasn't deleted successfully. - :raises ~azure.cosmos.errors.CosmosResourceNotFoundError: The permission does not exist for the user. + :keyword Callable response_hook: A callable invoked with the response metadata. + :raises ~azure.cosmos.exceptions.CosmosHttpResponseError: The permission wasn't deleted successfully. + :raises ~azure.cosmos.exceptions.CosmosResourceNotFoundError: The permission does not exist for the user. :rtype: None """ request_options = build_options(kwargs) diff --git a/sdk/cosmos/azure-cosmos/samples/MultiMasterOperations/ConflictWorker.py b/sdk/cosmos/azure-cosmos/samples/MultiMasterOperations/ConflictWorker.py index 40f64026ebec..6bdc3b2e6cc7 100644 --- a/sdk/cosmos/azure-cosmos/samples/MultiMasterOperations/ConflictWorker.py +++ b/sdk/cosmos/azure-cosmos/samples/MultiMasterOperations/ConflictWorker.py @@ -2,7 +2,7 @@ import time from multiprocessing.pool import ThreadPool import json -import azure.cosmos.errors as errors +import azure.cosmos.exceptions as exceptions from azure.cosmos.http_constants import StatusCodes class ConflictWorker(object): @@ -27,7 +27,7 @@ def initialize_async(self): database = None try: database = create_client.ReadDatabase("dbs/" + self.database_name) - except errors.CosmosResourceNotFoundError: + except exceptions.CosmosResourceNotFoundError: print("database not found, needs to be created.") if not database: @@ -119,14 +119,14 @@ def initialize_async(self): } try: lww_sproc = create_client.CreateStoredProcedure("dbs/" + self.database_name+ "/colls/" + self.udp_collection_name, lww_sproc) - except errors.CosmosResourceExistsError: + except exceptions.CosmosResourceExistsError: return def try_create_document_collection (self, client, database, collection): read_collection = None try: read_collection = client.ReadContainer("dbs/" + database['id'] + "/colls/" + collection['id']) - except errors.CosmosResourceNotFoundError: + except exceptions.CosmosResourceNotFoundError: print("collection not found, needs to be created.") if read_collection == None: @@ -473,14 +473,14 @@ def run_delete_conflict_on_UDP_async(self): def try_insert_document(self, client, collection_uri, document): try: return client.CreateItem(collection_uri, document) - except errors.CosmosResourceExistsError: + except exceptions.CosmosResourceExistsError: return None def try_update_document(self, client, collection_uri, document, options): try: options['partitionKey'] = document['id'] return client.ReplaceItem(collection_uri + "/docs/" + document['id'], document, options); - except (errors.CosmosResourceNotFoundError, errors.CosmosAccessConditionFailedError): + except (exceptions.CosmosResourceNotFoundError, exceptions.CosmosAccessConditionFailedError): # Lost synchronously or no document yet. No conflict is induced. return None @@ -489,7 +489,7 @@ def try_delete_document(self, client, collection_uri, document, options): options['partitionKey'] = document['id'] client.DeleteItem(collection_uri + "/docs/" + document['id'], options) return document - except (errors.CosmosResourceNotFoundError, errors.CosmosAccessConditionFailedError): + except (exceptions.CosmosResourceNotFoundError, exceptions.CosmosAccessConditionFailedError): #Lost synchronously. No conflict is induced. return None @@ -591,10 +591,10 @@ def validate_LWW_async_internal(self, client, conflict_document, has_delete_conf (conflict_document[0]['id'], client.ReadEndpoint)) time.sleep(0.5) - except errors.CosmosResourceNotFoundError: + except exceptions.CosmosResourceNotFoundError: print("Delete conflict won @ %s" % client.ReadEndpoint) return - except errors.CosmosHttpResponseError: + except exceptions.CosmosHttpResponseError: self.trace_error("Delete conflict for document %s didnt win @ %s" % (conflict_document[0]['id'], client.ReadEndpoint)) @@ -622,7 +622,7 @@ def validate_LWW_async_internal(self, client, conflict_document, has_delete_conf (int(winner_document["regionId"]), client.WriteEndpoint)) time.sleep(0.5) - except errors.AzureError as e: + except exceptions.AzureError as e: self.trace_error("Winner document from region %d is not found @ %s, retrying..." % (int(winner_document["regionId"]), client.WriteEndpoint)) @@ -655,10 +655,10 @@ def validate_UDP_async_internal(self, client, conflict_document, has_delete_conf (conflict_document[0]['id'], client.ReadEndpoint)) time.sleep(0.5) - except errors.CosmosResourceNotFoundError: + except exceptions.CosmosResourceNotFoundError: print("Delete conflict won @ %s" % client.ReadEndpoint) return - except errors.CosmosHttpResponseError: + except exceptions.CosmosHttpResponseError: self.trace_error("Delete conflict for document %s didnt win @ %s" % (conflict_document[0]['id'], client.ReadEndpoint)) time.sleep(0.5) @@ -685,7 +685,7 @@ def validate_UDP_async_internal(self, client, conflict_document, has_delete_conf (int(winner_document['regionId']), client.WriteEndpoint)) time.sleep(0.5) - except errors.AzureError: + except exceptions.AzureError: self.trace_error("Winner document from region %d is not found @ %s, retrying..." % (int(winner_document['regionId']), client.WriteEndpoint)) time.sleep(0.5) diff --git a/sdk/cosmos/azure-cosmos/samples/MultiMasterOperations/Worker.py b/sdk/cosmos/azure-cosmos/samples/MultiMasterOperations/Worker.py index 38af9a920314..c8d1dc1640bf 100644 --- a/sdk/cosmos/azure-cosmos/samples/MultiMasterOperations/Worker.py +++ b/sdk/cosmos/azure-cosmos/samples/MultiMasterOperations/Worker.py @@ -1,6 +1,6 @@ import uuid import time -import azure.cosmos.errors as errors +import azure.cosmos.exceptions as exceptions from azure.cosmos.http_constants import StatusCodes class Worker(object): @@ -62,9 +62,9 @@ def delete_all_async(self): while doc: try: self.client.DeleteItem(doc['_self'], {'partitionKey': doc['id']}) - except errors.CosmosResourceNotFoundError: + except exceptions.CosmosResourceNotFoundError: raise - except errors.CosmosHttpResponseError as e: + except exceptions.CosmosHttpResponseError as e: print("Error occurred while deleting document from %s" % self.client.WriteEndpoint) doc = next(it, None) diff --git a/sdk/cosmos/azure-cosmos/samples/__init__.py b/sdk/cosmos/azure-cosmos/samples/__init__.py deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/sdk/cosmos/azure-cosmos/samples/change_feed_management.py b/sdk/cosmos/azure-cosmos/samples/change_feed_management.py index b753dc058424..42427fb3dce7 100644 --- a/sdk/cosmos/azure-cosmos/samples/change_feed_management.py +++ b/sdk/cosmos/azure-cosmos/samples/change_feed_management.py @@ -1,6 +1,6 @@ import azure.cosmos.documents as documents import azure.cosmos.cosmos_client as cosmos_client -import azure.cosmos.errors as errors +import azure.cosmos.exceptions as exceptions import azure.cosmos.partition_key as partition_key import datetime import uuid @@ -60,7 +60,7 @@ def run_sample(): # setup database for this sample try: db = client.create_database(id=DATABASE_ID) - except errors.CosmosResourceExistsError: + except exceptions.CosmosResourceExistsError: raise RuntimeError("Database with id '{}' already exists".format(DATABASE_ID)) # setup container for this sample @@ -71,7 +71,7 @@ def run_sample(): ) print('Container with id \'{0}\' created'.format(CONTAINER_ID)) - except errors.CosmosResourceExistsError: + except exceptions.CosmosResourceExistsError: raise RuntimeError("Container with id '{}' already exists".format(CONTAINER_ID)) create_items(container, 100) @@ -80,10 +80,10 @@ def run_sample(): # cleanup database after sample try: client.delete_database(db) - except errors.CosmosResourceNotFoundError: + except exceptions.CosmosResourceNotFoundError: pass - except errors.CosmosHttpResponseError as e: + except exceptions.CosmosHttpResponseError as e: print('\nrun_sample has caught an error. {0}'.format(e.message)) finally: diff --git a/sdk/cosmos/azure-cosmos/samples/collection_management.py b/sdk/cosmos/azure-cosmos/samples/collection_management.py index a4a5821e3bb7..326fc8a6f1b8 100644 --- a/sdk/cosmos/azure-cosmos/samples/collection_management.py +++ b/sdk/cosmos/azure-cosmos/samples/collection_management.py @@ -1,5 +1,5 @@ import azure.cosmos.cosmos_client as cosmos_client -import azure.cosmos.errors as errors +import azure.cosmos.exceptions as exceptions from azure.cosmos.partition_key import PartitionKey import config @@ -77,7 +77,7 @@ def create_container(db, id): db.create_container(id=id, partition_key=partition_key) print('Container with id \'{0}\' created'.format(id)) - except errors.CosmosResourceExistsError: + except exceptions.CosmosResourceExistsError: print('A container with id \'{0}\' already exists'.format(id)) print("\n2.2 Create Container - With custom index policy") @@ -101,7 +101,7 @@ def create_container(db, id): print('IndexPolicy Mode - \'{0}\''.format(properties['indexingPolicy']['indexingMode'])) print('IndexPolicy Automatic - \'{0}\''.format(properties['indexingPolicy']['automatic'])) - except errors.CosmosResourceExistsError: + except exceptions.CosmosResourceExistsError: print('A container with id \'{0}\' already exists'.format(coll['id'])) print("\n2.3 Create Container - With custom offer throughput") @@ -115,7 +115,7 @@ def create_container(db, id): ) print('Container with id \'{0}\' created'.format(container.id)) - except errors.CosmosResourceExistsError: + except exceptions.CosmosResourceExistsError: print('A container with id \'{0}\' already exists'.format(coll['id'])) print("\n2.4 Create Container - With Unique keys") @@ -131,7 +131,7 @@ def create_container(db, id): print('Container with id \'{0}\' created'.format(container.id)) print('Unique Key Paths - \'{0}\', \'{1}\''.format(unique_key_paths[0], unique_key_paths[1])) - except errors.CosmosResourceExistsError: + except exceptions.CosmosResourceExistsError: print('A container with id \'container_unique_keys\' already exists') print("\n2.5 Create Collection - With Partition key V2 (Default)") @@ -145,7 +145,7 @@ def create_container(db, id): print('Container with id \'{0}\' created'.format(container.id)) print('Partition Key - \'{0}\''.format(properties['partitionKey'])) - except errors.CosmosResourceExistsError: + except exceptions.CosmosResourceExistsError: print('A container with id \'collection_partition_key_v2\' already exists') print("\n2.6 Create Collection - With Partition key V1") @@ -159,7 +159,7 @@ def create_container(db, id): print('Container with id \'{0}\' created'.format(container.id)) print('Partition Key - \'{0}\''.format(properties['partitionKey'])) - except errors.CosmosResourceExistsError: + except exceptions.CosmosResourceExistsError: print('A container with id \'collection_partition_key_v1\' already exists') @@ -180,7 +180,7 @@ def manage_offer_throughput(db, id): print('Found Offer \'{0}\' for Container \'{1}\' and its throughput is \'{2}\''.format(offer.properties['id'], container.id, offer.properties['content']['offerThroughput'])) - except errors.CosmosResourceExistsError: + except exceptions.CosmosResourceExistsError: print('A container with id \'{0}\' does not exist'.format(id)) print("\n3.2 Change Offer Throughput of Container") @@ -199,7 +199,7 @@ def read_Container(db, id): container = db.get_container_client(id) print('Container with id \'{0}\' was found, it\'s link is {1}'.format(container.id, container.container_link)) - except errors.CosmosResourceNotFoundError: + except exceptions.CosmosResourceNotFoundError: print('A container with id \'{0}\' does not exist'.format(id)) @@ -225,7 +225,7 @@ def delete_Container(db, id): print('Container with id \'{0}\' was deleted'.format(id)) - except errors.CosmosResourceNotFoundError: + except exceptions.CosmosResourceNotFoundError: print('A container with id \'{0}\' does not exist'.format(id)) @@ -237,7 +237,7 @@ def run_sample(): try: db = client.create_database(id=DATABASE_ID) - except errors.CosmosResourceExistsError: + except exceptions.CosmosResourceExistsError: db = client.get_database_client(DATABASE_ID) # query for a container @@ -262,10 +262,10 @@ def run_sample(): try: client.delete_database(db) - except errors.CosmosResourceNotFoundError: + except exceptions.CosmosResourceNotFoundError: pass - except errors.CosmosHttpResponseError as e: + except exceptions.CosmosHttpResponseError as e: print('\nrun_sample has caught an error. {0}'.format(e.message)) finally: diff --git a/sdk/cosmos/azure-cosmos/samples/database_management.py b/sdk/cosmos/azure-cosmos/samples/database_management.py index c62d249e7427..26645a6f1a08 100644 --- a/sdk/cosmos/azure-cosmos/samples/database_management.py +++ b/sdk/cosmos/azure-cosmos/samples/database_management.py @@ -1,5 +1,5 @@ import azure.cosmos.cosmos_client as cosmos_client -import azure.cosmos.errors as errors +import azure.cosmos.exceptions as exceptions import config @@ -52,7 +52,7 @@ def create_database(client, id): client.create_database(id=id) print('Database with id \'{0}\' created'.format(id)) - except errors.CosmosResourceExistsError: + except exceptions.CosmosResourceExistsError: print('A database with id \'{0}\' already exists'.format(id)) @@ -63,7 +63,7 @@ def read_database(client, id): database = client.get_database_client(id) print('Database with id \'{0}\' was found, it\'s link is {1}'.format(id, database.database_link)) - except errors.CosmosResourceNotFoundError: + except exceptions.CosmosResourceNotFoundError: print('A database with id \'{0}\' does not exist'.format(id)) @@ -89,7 +89,7 @@ def delete_database(client, id): print('Database with id \'{0}\' was deleted'.format(id)) - except errors.CosmosResourceNotFoundError: + except exceptions.CosmosResourceNotFoundError: print('A database with id \'{0}\' does not exist'.format(id)) @@ -111,7 +111,7 @@ def run_sample(): # delete database by id delete_database(client, DATABASE_ID) - except errors.CosmosHttpResponseError as e: + except exceptions.CosmosHttpResponseError as e: print('\nrun_sample has caught an error. {0}'.format(e.message)) finally: diff --git a/sdk/cosmos/azure-cosmos/samples/document_management.py b/sdk/cosmos/azure-cosmos/samples/document_management.py index 4a627ee6a9c1..82f36d40852d 100644 --- a/sdk/cosmos/azure-cosmos/samples/document_management.py +++ b/sdk/cosmos/azure-cosmos/samples/document_management.py @@ -1,5 +1,5 @@ import azure.cosmos.cosmos_client as cosmos_client -import azure.cosmos.errors as errors +import azure.cosmos.exceptions as exceptions from azure.cosmos.partition_key import PartitionKey import datetime @@ -163,7 +163,7 @@ def run_sample(): try: db = client.create_database(id=DATABASE_ID) - except errors.CosmosResourceExistsError: + except exceptions.CosmosResourceExistsError: pass # setup container for this sample @@ -171,7 +171,7 @@ def run_sample(): container = db.create_container(id=CONTAINER_ID, partition_key=PartitionKey(path='/id', kind='Hash')) print('Container with id \'{0}\' created'.format(CONTAINER_ID)) - except errors.CosmosResourceExistsError: + except exceptions.CosmosResourceExistsError: print('Container with id \'{0}\' was found'.format(CONTAINER_ID)) create_items(container) @@ -186,10 +186,10 @@ def run_sample(): try: client.delete_database(db) - except errors.CosmosResourceNotFoundError: + except exceptions.CosmosResourceNotFoundError: pass - except errors.CosmosHttpResponseError as e: + except exceptions.CosmosHttpResponseError as e: print('\nrun_sample has caught an error. {0}'.format(e.message)) finally: diff --git a/sdk/cosmos/azure-cosmos/samples/examples.py b/sdk/cosmos/azure-cosmos/samples/examples.py index ef20ef5f31bc..162203071c62 100644 --- a/sdk/cosmos/azure-cosmos/samples/examples.py +++ b/sdk/cosmos/azure-cosmos/samples/examples.py @@ -5,7 +5,7 @@ # All interaction with Cosmos DB starts with an instance of the CosmosClient # [START create_client] -from azure.cosmos import errors, CosmosClient, PartitionKey +from azure.cosmos import exceptions, CosmosClient, PartitionKey import os @@ -21,7 +21,7 @@ database_name = "testDatabase" try: database = client.create_database(id=database_name) -except errors.CosmosResourceExistsError: +except exceptions.CosmosResourceExistsError: database = client.get_database_client(database=database_name) # [END create_database] @@ -33,7 +33,7 @@ container = database.create_container( id=container_name, partition_key=PartitionKey(path="/productName") ) -except errors.CosmosResourceExistsError: +except exceptions.CosmosResourceExistsError: container = database.get_container_client(container_name) # [END create_container] @@ -47,7 +47,7 @@ partition_key=PartitionKey(path="/city"), default_ttl=200, ) -except errors.CosmosResourceExistsError: +except exceptions.CosmosResourceExistsError: customer_container = database.get_container_client(customer_container_name) # [END create_container_with_settings] @@ -138,8 +138,8 @@ # [START create_user] try: database.create_user(dict(id="Walter Harp")) -except errors.CosmosResourceExistsError: +except exceptions.CosmosResourceExistsError: print("A user with that ID already exists.") -except errors.CosmosHttpResponseError as failure: +except exceptions.CosmosHttpResponseError as failure: print("Failed to create user. Status code:{}".format(failure.status_code)) # [END create_user] diff --git a/sdk/cosmos/azure-cosmos/samples/index_management.py b/sdk/cosmos/azure-cosmos/samples/index_management.py index 6bbb2642e135..5b2580fd556d 100644 --- a/sdk/cosmos/azure-cosmos/samples/index_management.py +++ b/sdk/cosmos/azure-cosmos/samples/index_management.py @@ -1,6 +1,6 @@ import azure.cosmos.documents as documents import azure.cosmos.cosmos_client as cosmos_client -import azure.cosmos.errors as errors +import azure.cosmos.exceptions as exceptions from azure.cosmos.partition_key import PartitionKey import requests import traceback @@ -73,7 +73,7 @@ def query_entities(parent, entity_type, id = None): entities = list(parent.read_all_items()) else: entities = list(parent.query_items(find_entity_by_id_query)) - except errors.AzureError as e: + except exceptions.AzureError as e: print("The following error occured while querying for the entity / entities ", entity_type, id if id != None else "") print(e) raise @@ -91,7 +91,7 @@ def create_database_if_not_exists(client, database_id): return client.create_database(id=database_id) else: return client.get_database_client(database_id) - except errors.CosmosResourceExistsError: + except exceptions.CosmosResourceExistsError: pass @@ -99,9 +99,9 @@ def delete_container_if_exists(db, collection_id): try: db.delete_container(collection_id) print('Collection with id \'{0}\' was deleted'.format(collection_id)) - except errors.CosmosResourceNotFoundError: + except exceptions.CosmosResourceNotFoundError: pass - except errors.CosmosHttpResponseError as e: + except exceptions.CosmosHttpResponseError as e: if e.status_code == 400: print("Bad request for collection link", collection_id) raise @@ -129,9 +129,9 @@ def query_documents_with_custom_query(container, query_with_optional_parameters, for doc in results: print(doc) return results - except errors.CosmosResourceNotFoundError: + except exceptions.CosmosResourceNotFoundError: print("Document doesn't exist") - except errors.CosmosHttpResponseError as e: + except exceptions.CosmosHttpResponseError as e: if e.status_code == 400: # Can occur when we are trying to query on excluded paths print("Bad Request exception occured: ", e) @@ -195,9 +195,9 @@ def explicitly_exclude_from_index(db): # Cleanup db.delete_container(created_Container) print("\n") - except errors.CosmosResourceExistsError: + except exceptions.CosmosResourceExistsError: print("Entity already exists") - except errors.CosmosResourceNotFoundError: + except exceptions.CosmosResourceNotFoundError: print("Entity doesn't exist") @@ -257,9 +257,9 @@ def use_manual_indexing(db): # Cleanup db.delete_container(created_Container) print("\n") - except errors.CosmosResourceExistsError: + except exceptions.CosmosResourceExistsError: print("Entity already exists") - except errors.CosmosResourceNotFoundError: + except exceptions.CosmosResourceNotFoundError: print("Entity doesn't exist") @@ -330,9 +330,9 @@ def exclude_paths_from_index(db): # Cleanup db.delete_container(created_Container) print("\n") - except errors.CosmosResourceExistsError: + except exceptions.CosmosResourceExistsError: print("Entity already exists") - except errors.CosmosResourceNotFoundError: + except exceptions.CosmosResourceNotFoundError: print("Entity doesn't exist") @@ -392,9 +392,9 @@ def range_scan_on_hash_index(db): # Cleanup db.delete_container(created_Container) print("\n") - except errors.CosmosResourceExistsError: + except exceptions.CosmosResourceExistsError: print("Entity already exists") - except errors.CosmosResourceNotFoundError: + except exceptions.CosmosResourceNotFoundError: print("Entity doesn't exist") @@ -479,9 +479,9 @@ def use_range_indexes_on_strings(db): # Cleanup db.delete_container(created_Container) print("\n") - except errors.CosmosResourceExistsError: + except exceptions.CosmosResourceExistsError: print("Entity already exists") - except errors.CosmosResourceNotFoundError: + except exceptions.CosmosResourceNotFoundError: print("Entity doesn't exist") @@ -538,9 +538,9 @@ def perform_index_transformations(db): # Cleanup db.delete_container(created_Container) print("\n") - except errors.CosmosResourceExistsError: + except exceptions.CosmosResourceExistsError: print("Entity already exists") - except errors.CosmosResourceNotFoundError: + except exceptions.CosmosResourceNotFoundError: print("Entity doesn't exist") @@ -629,9 +629,9 @@ def perform_multi_orderby_query(db): # Cleanup db.delete_container(created_container) print("\n") - except errors.CosmosResourceExistsError: + except exceptions.CosmosResourceExistsError: print("Entity already exists") - except errors.CosmosResourceNotFoundError: + except exceptions.CosmosResourceNotFoundError: print("Entity doesn't exist") @@ -665,7 +665,7 @@ def run_sample(): # 8. Perform Multi Orderby queries using composite indexes perform_multi_orderby_query(created_db) - except errors.AzureError as e: + except exceptions.AzureError as e: raise e if __name__ == '__main__': diff --git a/sdk/cosmos/azure-cosmos/samples/nonpartitioned_collection_operations.py b/sdk/cosmos/azure-cosmos/samples/nonpartitioned_collection_operations.py index f7532bdcc2bb..9d57e2e83360 100644 --- a/sdk/cosmos/azure-cosmos/samples/nonpartitioned_collection_operations.py +++ b/sdk/cosmos/azure-cosmos/samples/nonpartitioned_collection_operations.py @@ -20,7 +20,7 @@ #SOFTWARE. import azure.cosmos.cosmos_client as cosmos_client -import azure.cosmos.errors as errors +import azure.cosmos.exceptions as exceptions import requests import six import json @@ -268,7 +268,7 @@ def run_sample(): # setup database for this sample try: db = client.create_database(id=DATABASE_ID) - except errors.CosmosResourceExistsError: + except exceptions.CosmosResourceExistsError: db = client.get_database_client(DATABASE_ID) # setup container for this sample @@ -276,7 +276,7 @@ def run_sample(): container, document = create_nonpartitioned_collection(db) print('Container with id \'{0}\' created'.format(CONTAINER_ID)) - except errors.CosmosResourceExistsError: + except exceptions.CosmosResourceExistsError: print('Container with id \'{0}\' was found'.format(CONTAINER_ID)) # Read Item created in non partitioned collection using older API version @@ -291,10 +291,10 @@ def run_sample(): # cleanup database after sample try: client.delete_database(db) - except errors.CosmosResourceNotFoundError: + except exceptions.CosmosResourceNotFoundError: pass - except errors.CosmosHttpResponseError as e: + except exceptions.CosmosHttpResponseError as e: print('\nrun_sample has caught an error. {0}'.format(e.message)) finally: diff --git a/sdk/cosmos/azure-cosmos/setup.py b/sdk/cosmos/azure-cosmos/setup.py index 5fef99e8e5f5..642b613f6e8c 100644 --- a/sdk/cosmos/azure-cosmos/setup.py +++ b/sdk/cosmos/azure-cosmos/setup.py @@ -21,7 +21,7 @@ NAMESPACE_NAME = PACKAGE_NAME.replace("-", ".") # Version extraction inspired from 'requests' -with open(os.path.join(PACKAGE_FOLDER_PATH, 'version.py'), 'r') as fd: +with open(os.path.join(PACKAGE_FOLDER_PATH, '_version.py'), 'r') as fd: version = re.search(r'^VERSION\s*=\s*[\'"]([^\'"]*)[\'"]', fd.read(), re.MULTILINE).group(1) diff --git a/sdk/cosmos/azure-cosmos/test/aggregate_tests.py b/sdk/cosmos/azure-cosmos/test/aggregate_tests.py index a10343334234..dc32684aa057 100644 --- a/sdk/cosmos/azure-cosmos/test/aggregate_tests.py +++ b/sdk/cosmos/azure-cosmos/test/aggregate_tests.py @@ -31,7 +31,7 @@ import azure.cosmos.cosmos_client as cosmos_client import azure.cosmos.documents as documents import test_config -from azure.cosmos.errors import CosmosHttpResponseError +from azure.cosmos.exceptions import CosmosHttpResponseError from azure.cosmos.partition_key import PartitionKey pytestmark = pytest.mark.cosmosEmulator @@ -49,156 +49,156 @@ class _config: sum = 0 -class AggregateQueryTestSequenceMeta(type): - def __new__(mcs, name, bases, dict): - def _run_one(query, expected_result): - def test(self): - self._execute_query_and_validate_results(mcs.created_collection, query, expected_result) - - return test - - def _setup(): - if (not _config.master_key or not _config.host): - raise Exception( - "You must specify your Azure Cosmos account values for " - "'masterKey' and 'host' at the top of this class to run the " - "tests.") - - mcs.client = cosmos_client.CosmosClient( - _config.host, _config.master_key, "Session", connection_policy=_config.connection_policy) - created_db = test_config._test_config.create_database_if_not_exist(mcs.client) - mcs.created_collection = _create_collection(created_db) - - # test documents - document_definitions = [] - - values = [None, False, True, "abc", "cdfg", "opqrs", "ttttttt", "xyz", "oo", "ppp"] - for value in values: - d = {_config.PARTITION_KEY: value, 'id': str(uuid.uuid4())} - document_definitions.append(d) - - for i in xrange(_config.DOCS_WITH_SAME_PARTITION_KEY): - d = {_config.PARTITION_KEY: _config.UNIQUE_PARTITION_KEY, - 'resourceId': i, - _config.FIELD: i + 1, - 'id': str(uuid.uuid4())} - document_definitions.append(d) - - _config.docs_with_numeric_id = \ - _config.DOCUMENTS_COUNT - len(values) - _config.DOCS_WITH_SAME_PARTITION_KEY - for i in xrange(_config.docs_with_numeric_id): - d = {_config.PARTITION_KEY: i + 1, 'id': str(uuid.uuid4())} - document_definitions.append(d) - - _config.sum = _config.docs_with_numeric_id \ - * (_config.docs_with_numeric_id + 1) / 2.0 - - _insert_doc(mcs.created_collection, document_definitions) - - def _generate_test_configs(): - aggregate_query_format = 'SELECT VALUE {}(r.{}) FROM r WHERE {}' - aggregate_orderby_query_format = 'SELECT VALUE {}(r.{}) FROM r WHERE {} ORDER BY r.{}' - aggregate_configs = [ - ['AVG', _config.sum / _config.docs_with_numeric_id, - 'IS_NUMBER(r.{})'.format(_config.PARTITION_KEY)], - ['AVG', None, 'true'], - ['COUNT', _config.DOCUMENTS_COUNT, 'true'], - ['MAX', 'xyz', 'true'], - ['MIN', None, 'true'], - ['SUM', _config.sum, 'IS_NUMBER(r.{})'.format(_config.PARTITION_KEY)], - ['SUM', None, 'true'] - ] - for operator, expected, condition in aggregate_configs: - _all_tests.append([ - '{} {}'.format(operator, condition), - aggregate_query_format.format(operator, _config.PARTITION_KEY, condition), - expected]) - _all_tests.append([ - '{} {} OrderBy'.format(operator, condition), - aggregate_orderby_query_format.format(operator, _config.PARTITION_KEY, condition, - _config.PARTITION_KEY), - expected]) - - aggregate_single_partition_format = 'SELECT VALUE {}(r.{}) FROM r WHERE r.{} = \'{}\'' - aggregate_orderby_single_partition_format = 'SELECT {}(r.{}) FROM r WHERE r.{} = \'{}\'' - same_partiton_sum = _config.DOCS_WITH_SAME_PARTITION_KEY * (_config.DOCS_WITH_SAME_PARTITION_KEY + 1) / 2.0 - aggregate_single_partition_configs = [ - ['AVG', same_partiton_sum / _config.DOCS_WITH_SAME_PARTITION_KEY], - ['COUNT', _config.DOCS_WITH_SAME_PARTITION_KEY], - ['MAX', _config.DOCS_WITH_SAME_PARTITION_KEY], - ['MIN', 1], - ['SUM', same_partiton_sum] - ] - for operator, expected in aggregate_single_partition_configs: - _all_tests.append([ - '{} SinglePartition {}'.format(operator, 'SELECT VALUE'), - aggregate_single_partition_format.format( - operator, _config.FIELD, _config.PARTITION_KEY, _config.UNIQUE_PARTITION_KEY), expected]) - _all_tests.append([ - '{} SinglePartition {}'.format(operator, 'SELECT'), - aggregate_orderby_single_partition_format.format( - operator, _config.FIELD, _config.PARTITION_KEY, _config.UNIQUE_PARTITION_KEY), - Exception()]) - - def _run_all(): - for test_name, query, expected_result in _all_tests: - test_name = "test_%s" % test_name - dict[test_name] = _run_one(query, expected_result) - - def _create_collection(created_db): - # type: (Database) -> Container - created_collection = created_db.create_container( - id='aggregate tests collection ' + str(uuid.uuid4()), - indexing_policy={ - 'includedPaths': [ - { - 'path': '/', - 'indexes': [ - { - 'kind': 'Range', - 'dataType': 'Number' - }, - { - 'kind': 'Range', - 'dataType': 'String' - } - ] - } - ] - }, - partition_key=PartitionKey( - path='/{}'.format(_config.PARTITION_KEY), - kind=documents.PartitionKind.Hash, - ), - offer_throughput=10100 - ) - - return created_collection - - def _insert_doc(collection, document_definitions): - # type: (Container, Dict[str, Any]) -> [Dict[str, Any]] - created_docs = [] - for d in document_definitions: - print(d) - created_doc = collection.create_item(body=d) - created_docs.append(created_doc) - - return created_docs - - _all_tests = [] - - return type.__new__(mcs, name, bases, dict) +@pytest.mark.usefixtures("teardown") +class AggregationQueryTest(unittest.TestCase): + @classmethod + def setUpClass(cls): + cls._all_tests = [] + cls._setup() + cls._generate_test_configs() + + @classmethod + def _setup(cls): + if (not _config.master_key or not _config.host): + raise Exception( + "You must specify your Azure Cosmos account values for " + "'masterKey' and 'host' at the top of this class to run the " + "tests.") + + cls.client = cosmos_client.CosmosClient( + _config.host, {'masterKey': _config.master_key}, "Session", connection_policy=_config.connection_policy) + created_db = test_config._test_config.create_database_if_not_exist(cls.client) + cls.created_collection = cls._create_collection(created_db) + + # test documents + document_definitions = [] + + values = [None, False, True, "abc", "cdfg", "opqrs", "ttttttt", "xyz", "oo", "ppp"] + for value in values: + d = {_config.PARTITION_KEY: value, 'id': str(uuid.uuid4())} + document_definitions.append(d) + + for i in xrange(_config.DOCS_WITH_SAME_PARTITION_KEY): + d = {_config.PARTITION_KEY: _config.UNIQUE_PARTITION_KEY, + 'resourceId': i, + _config.FIELD: i + 1, + 'id': str(uuid.uuid4())} + document_definitions.append(d) + + _config.docs_with_numeric_id = \ + _config.DOCUMENTS_COUNT - len(values) - _config.DOCS_WITH_SAME_PARTITION_KEY + for i in xrange(_config.docs_with_numeric_id): + d = {_config.PARTITION_KEY: i + 1, 'id': str(uuid.uuid4())} + document_definitions.append(d) + + _config.sum = _config.docs_with_numeric_id \ + * (_config.docs_with_numeric_id + 1) / 2.0 + + cls._insert_doc(cls.created_collection, document_definitions) + + @classmethod + def _generate_test_configs(cls): + aggregate_query_format = 'SELECT VALUE {}(r.{}) FROM r WHERE {}' + aggregate_orderby_query_format = 'SELECT VALUE {}(r.{}) FROM r WHERE {} ORDER BY r.{}' + aggregate_configs = [ + ['AVG', _config.sum / _config.docs_with_numeric_id, + 'IS_NUMBER(r.{})'.format(_config.PARTITION_KEY)], + ['AVG', None, 'true'], + ['COUNT', _config.DOCUMENTS_COUNT, 'true'], + ['MAX', 'xyz', 'true'], + ['MIN', None, 'true'], + ['SUM', _config.sum, 'IS_NUMBER(r.{})'.format(_config.PARTITION_KEY)], + ['SUM', None, 'true'] + ] + for operator, expected, condition in aggregate_configs: + cls._all_tests.append([ + '{} {}'.format(operator, condition), + aggregate_query_format.format(operator, _config.PARTITION_KEY, condition), + expected]) + cls._all_tests.append([ + '{} {} OrderBy'.format(operator, condition), + aggregate_orderby_query_format.format(operator, _config.PARTITION_KEY, condition, + _config.PARTITION_KEY), + expected]) + + aggregate_single_partition_format = 'SELECT VALUE {}(r.{}) FROM r WHERE r.{} = \'{}\'' + aggregate_orderby_single_partition_format = 'SELECT {}(r.{}) FROM r WHERE r.{} = \'{}\'' + same_partiton_sum = _config.DOCS_WITH_SAME_PARTITION_KEY * (_config.DOCS_WITH_SAME_PARTITION_KEY + 1) / 2.0 + aggregate_single_partition_configs = [ + ['AVG', same_partiton_sum / _config.DOCS_WITH_SAME_PARTITION_KEY], + ['COUNT', _config.DOCS_WITH_SAME_PARTITION_KEY], + ['MAX', _config.DOCS_WITH_SAME_PARTITION_KEY], + ['MIN', 1], + ['SUM', same_partiton_sum] + ] + for operator, expected in aggregate_single_partition_configs: + cls._all_tests.append([ + '{} SinglePartition {}'.format(operator, 'SELECT VALUE'), + aggregate_single_partition_format.format( + operator, _config.FIELD, _config.PARTITION_KEY, _config.UNIQUE_PARTITION_KEY), expected]) + cls._all_tests.append([ + '{} SinglePartition {}'.format(operator, 'SELECT'), + aggregate_orderby_single_partition_format.format( + operator, _config.FIELD, _config.PARTITION_KEY, _config.UNIQUE_PARTITION_KEY), + Exception()]) + + def test_run_all(self): + for test_name, query, expected_result in self._all_tests: + test_name = "test_%s" % test_name + try: + self._run_one(query, expected_result) + print(test_name + ': ' + query + " PASSED") + except Exception as e: + print(test_name + ': ' + query + " FAILED") + raise e + + def _run_one(self, query, expected_result): + self._execute_query_and_validate_results(self.created_collection, query, expected_result) + + @classmethod + def _create_collection(cls, created_db): + # type: (Database) -> Container + created_collection = created_db.create_container( + id='aggregate tests collection ' + str(uuid.uuid4()), + indexing_policy={ + 'includedPaths': [ + { + 'path': '/', + 'indexes': [ + { + 'kind': 'Range', + 'dataType': 'Number' + }, + { + 'kind': 'Range', + 'dataType': 'String' + } + ] + } + ] + }, + partition_key=PartitionKey( + path='/{}'.format(_config.PARTITION_KEY), + kind=documents.PartitionKind.Hash, + ), + offer_throughput=10100 + ) + return created_collection + @classmethod + def _insert_doc(cls, collection, document_definitions): + # type: (Container, Dict[str, Any]) -> [Dict[str, Any]] + created_docs = [] + for d in document_definitions: + created_doc = collection.create_item(body=d) + created_docs.append(created_doc) + + return created_docs -@pytest.mark.usefixtures("teardown") -class AggregationQueryTest(with_metaclass(AggregateQueryTestSequenceMeta, unittest.TestCase)): def _execute_query_and_validate_results(self, collection, query, expected): # type: (Container, str, [Dict[str, Any]]) -> None - print('Running test with query: ' + query) # executes the query and validates the results against the expected results - options = {'enableCrossPartitionQuery': 'true'} - result_iterable = collection.query_items( query=query, enable_cross_partition_query=True @@ -239,5 +239,6 @@ def invokeNext(): else: _verify_result() + if __name__ == "__main__": unittest.main() diff --git a/sdk/cosmos/azure-cosmos/test/conftest.py b/sdk/cosmos/azure-cosmos/test/conftest.py index cbb2191bc775..a838918fcbb0 100644 --- a/sdk/cosmos/azure-cosmos/test/conftest.py +++ b/sdk/cosmos/azure-cosmos/test/conftest.py @@ -24,7 +24,7 @@ import pytest import test_config import azure.cosmos.cosmos_client as cosmos_client -import azure.cosmos.errors as errors +import azure.cosmos.exceptions as exceptions from azure.cosmos.http_constants import StatusCodes database_ids_to_delete = [] @@ -48,7 +48,7 @@ def delete_database(): for database_id in database_ids_to_delete: try: client.delete_database(database_id) - except errors.CosmosResourceNotFoundError: + except exceptions.CosmosResourceNotFoundError: pass del database_ids_to_delete[:] print("Clean up completed!") diff --git a/sdk/cosmos/azure-cosmos/test/crud_tests.py b/sdk/cosmos/azure-cosmos/test/crud_tests.py index cd2d2934f90f..c9620430c8a5 100644 --- a/sdk/cosmos/azure-cosmos/test/crud_tests.py +++ b/sdk/cosmos/azure-cosmos/test/crud_tests.py @@ -41,11 +41,12 @@ import urllib.parse as urllib import uuid import pytest +from azure.core import MatchConditions from azure.core.exceptions import AzureError, ServiceResponseError from azure.core.pipeline.transport import RequestsTransport, RequestsTransportResponse from azure.cosmos import _consistent_hash_ring import azure.cosmos.documents as documents -import azure.cosmos.errors as errors +import azure.cosmos.exceptions as exceptions from azure.cosmos.http_constants import HttpHeaders, StatusCodes, SubStatusCodes import azure.cosmos._murmur_hash as _murmur_hash import test_config @@ -110,7 +111,7 @@ def __AssertHTTPFailureWithStatus(self, status_code, func, *args, **kwargs): try: func(*args, **kwargs) self.assertFalse(True, 'function should fail.') - except errors.CosmosHttpResponseError as inst: + except exceptions.CosmosHttpResponseError as inst: self.assertEqual(inst.status_code, status_code) @classmethod @@ -857,9 +858,40 @@ def test_document_crud(self): if_match=old_etag, ) + # should fail if only etag specified + with self.assertRaises(ValueError): + created_collection.replace_item( + etag=replaced_document['_etag'], + item=replaced_document['id'], + body=replaced_document + ) + + # should fail if only match condition specified + with self.assertRaises(ValueError): + created_collection.replace_item( + match_condition=MatchConditions.IfNotModified, + item=replaced_document['id'], + body=replaced_document + ) + with self.assertRaises(ValueError): + created_collection.replace_item( + match_condition=MatchConditions.IfModified, + item=replaced_document['id'], + body=replaced_document + ) + + # should fail if invalid match condition specified + with self.assertRaises(TypeError): + created_collection.replace_item( + match_condition=replaced_document['_etag'], + item=replaced_document['id'], + body=replaced_document + ) + # should pass for most recent etag replaced_document_conditional = created_collection.replace_item( - access_condition={'type': 'IfMatch', 'condition': replaced_document['_etag']}, + match_condition=MatchConditions.IfNotModified, + etag=replaced_document['_etag'], item=replaced_document['id'], body=replaced_document ) @@ -1908,78 +1940,17 @@ def test_create_indexing_policy_with_composite_and_spatial_indexes(self): ) created_properties = created_container.read() read_indexing_policy = created_properties['indexingPolicy'] - self.assertListEqual(indexing_policy['spatialIndexes'], read_indexing_policy['spatialIndexes']) - self.assertListEqual(indexing_policy['compositeIndexes'], read_indexing_policy['compositeIndexes']) - db.delete_container(container=created_container) - - def disabled_test_create_indexing_policy_with_composite_and_spatial_indexes_self_link(self): - self._test_create_indexing_policy_with_composite_and_spatial_indexes(False) - - def disabled_test_create_indexing_policy_with_composite_and_spatial_indexes_name_based(self): - self._test_create_indexing_policy_with_composite_and_spatial_indexes(True) - def _test_create_indexing_policy_with_composite_and_spatial_indexes(self, is_name_based): - # create database - db = self.databaseForTest - - indexing_policy = { - "spatialIndexes": [ - { - "path": "/path0/*", - "types": [ - "Point", - "LineString", - "Polygon" - ] - }, - { - "path": "/path1/*", - "types": [ - "LineString", - "Polygon", - "MultiPolygon" - ] - } - ], - "compositeIndexes": [ - [ - { - "path": "/path1", - "order": "ascending" - }, - { - "path": "/path2", - "order": "descending" - }, - { - "path": "/path3", - "order": "ascending" - } - ], - [ - { - "path": "/path4", - "order": "ascending" - }, - { - "path": "/path5", - "order": "descending" - }, - { - "path": "/path6", - "order": "ascending" - } - ] - ] - } + if 'localhost' in self.host or '127.0.0.1' in self.host: # TODO: Differing result between live and emulator + self.assertListEqual(indexing_policy['spatialIndexes'], read_indexing_policy['spatialIndexes']) + else: + # All types are returned for spatial Indexes + indexing_policy['spatialIndexes'][0]['types'].append('MultiPolygon') + indexing_policy['spatialIndexes'][1]['types'].insert(0, 'Point') + self.assertListEqual(indexing_policy['spatialIndexes'], read_indexing_policy['spatialIndexes']) - container_id = 'composite_index_spatial_index' + str(uuid.uuid4()) - container_definition = {'id': container_id, 'indexingPolicy': indexing_policy} - created_container = self.client.CreateContainer(self.GetDatabaseLink(db, is_name_based), container_definition) - read_indexing_policy = created_container['indexingPolicy'] - self.assertListEqual(indexing_policy['spatialIndexes'], read_indexing_policy['spatialIndexes']) self.assertListEqual(indexing_policy['compositeIndexes'], read_indexing_policy['compositeIndexes']) - self.client.DeleteContainer(created_container['_self']) + db.delete_container(container=created_container) def _check_default_indexing_policy_paths(self, indexing_policy): def __get_first(array): @@ -2066,7 +2037,7 @@ def initialize_client_with_connection_core_retry_config(self, retries): return end_time - start_time def test_absolute_client_timeout(self): - with self.assertRaises(errors.CosmosClientTimeoutError): + with self.assertRaises(exceptions.CosmosClientTimeoutError): cosmos_client.CosmosClient( "https://localhost:9999", CRUDTests.masterKey, @@ -2079,32 +2050,31 @@ def test_absolute_client_timeout(self): client = cosmos_client.CosmosClient( self.host, self.masterKey, "Session", transport=timeout_transport, passthrough=True) - with self.assertRaises(errors.CosmosClientTimeoutError): + with self.assertRaises(exceptions.CosmosClientTimeoutError): client.create_database_if_not_exists("test", timeout=2) status_response = 500 # Users connection level retry timeout_transport = TimeoutTransport(status_response) client = cosmos_client.CosmosClient( self.host, self.masterKey, "Session", transport=timeout_transport, passthrough=True) - with self.assertRaises(errors.CosmosClientTimeoutError): + with self.assertRaises(exceptions.CosmosClientTimeoutError): client.create_database("test", timeout=2) databases = client.list_databases(timeout=2) - with self.assertRaises(errors.CosmosClientTimeoutError): + with self.assertRaises(exceptions.CosmosClientTimeoutError): list(databases) status_response = 429 # Uses Cosmos custom retry timeout_transport = TimeoutTransport(status_response) client = cosmos_client.CosmosClient( self.host, self.masterKey, "Session", transport=timeout_transport, passthrough=True) - with self.assertRaises(errors.CosmosClientTimeoutError): + with self.assertRaises(exceptions.CosmosClientTimeoutError): client.create_database_if_not_exists("test", timeout=2) databases = client.list_databases(timeout=2) - with self.assertRaises(errors.CosmosClientTimeoutError): + with self.assertRaises(exceptions.CosmosClientTimeoutError): list(databases) - def test_query_iterable_functionality(self): def __create_resources(client): """Creates resources for this test. diff --git a/sdk/cosmos/azure-cosmos/test/globaldb_mock_tests.py b/sdk/cosmos/azure-cosmos/test/globaldb_mock_tests.py index 98e9e2e68bb7..0cf275108644 100644 --- a/sdk/cosmos/azure-cosmos/test/globaldb_mock_tests.py +++ b/sdk/cosmos/azure-cosmos/test/globaldb_mock_tests.py @@ -25,7 +25,7 @@ import azure.cosmos._cosmos_client_connection as cosmos_client_connection import azure.cosmos.documents as documents -import azure.cosmos.errors as errors +import azure.cosmos.exceptions as exceptions import azure.cosmos._constants as constants from azure.cosmos.http_constants import StatusCodes import azure.cosmos._global_endpoint_manager as global_endpoint_manager @@ -152,13 +152,13 @@ def MockExecuteFunction(self, function, *args, **kwargs): else: self.endpoint_discovery_retry_count += 1 location_changed = True - raise errors.CosmosHttpResponseError( + raise exceptions.CosmosHttpResponseError( status_code=StatusCodes.FORBIDDEN, message="Forbidden", response=test_config.FakeResponse({'x-ms-substatus' : 3})) def MockGetDatabaseAccountStub(self, endpoint): - raise errors.CosmosHttpResponseError( + raise exceptions.CosmosHttpResponseError( status_code=StatusCodes.SERVICE_UNAVAILABLE, message="Service unavailable") def MockCreateDatabase(self, client, database): diff --git a/sdk/cosmos/azure-cosmos/test/globaldb_tests.py b/sdk/cosmos/azure-cosmos/test/globaldb_tests.py index 00c29b334532..467a0de40e6d 100644 --- a/sdk/cosmos/azure-cosmos/test/globaldb_tests.py +++ b/sdk/cosmos/azure-cosmos/test/globaldb_tests.py @@ -28,7 +28,7 @@ import azure.cosmos._cosmos_client_connection as cosmos_client_connection import azure.cosmos.documents as documents -import azure.cosmos.errors as errors +import azure.cosmos.exceptions as exceptions import azure.cosmos._global_endpoint_manager as global_endpoint_manager from azure.cosmos import _endpoint_discovery_retry_policy from azure.cosmos import _retry_utility @@ -72,7 +72,7 @@ def __AssertHTTPFailureWithStatus(self, status_code, sub_status, func, *args, ** try: func(*args, **kwargs) self.assertFalse(True, 'function should fail.') - except errors.CosmosHttpResponseError as inst: + except exceptions.CosmosHttpResponseError as inst: self.assertEqual(inst.status_code, status_code) self.assertEqual(inst.sub_status, sub_status) @@ -386,7 +386,7 @@ def test_globaldb_endpoint_discovery_retry_policy_mock(self): def _MockExecuteFunction(self, function, *args, **kwargs): response = test_config.FakeResponse({'x-ms-substatus' : SubStatusCodes.WRITE_FORBIDDEN}) - raise errors.CosmosHttpResponseError( + raise exceptions.CosmosHttpResponseError( status_code=StatusCodes.FORBIDDEN, message="Write Forbidden", response=response) diff --git a/sdk/cosmos/azure-cosmos/test/location_cache_tests.py b/sdk/cosmos/azure-cosmos/test/location_cache_tests.py index 7b5479bf22bf..9d53303ca4ea 100644 --- a/sdk/cosmos/azure-cosmos/test/location_cache_tests.py +++ b/sdk/cosmos/azure-cosmos/test/location_cache_tests.py @@ -8,7 +8,7 @@ import azure.cosmos.documents as documents from azure.cosmos._request_object import RequestObject from azure.cosmos._location_cache import LocationCache -import azure.cosmos.errors as errors +import azure.cosmos.exceptions as exceptions from azure.cosmos.http_constants import StatusCodes, SubStatusCodes, HttpHeaders from azure.cosmos import _retry_utility import test_config @@ -89,7 +89,7 @@ def validate_retry_on_session_not_availabe_with_endpoint_discovery_disabled(self else: client.CreateItem("dbs/mydb/colls/mycoll/", {'id':'1'}) self.fail() - except errors.CosmosHttpResponseError as e: + except exceptions.CosmosHttpResponseError as e: # not retried self.assertEqual(self.counter, 1) self.counter = 0 @@ -102,7 +102,7 @@ def validate_retry_on_session_not_availabe_with_endpoint_discovery_disabled(self def _MockExecuteFunctionSessionReadFailureOnce(self, function, *args, **kwargs): self.counter += 1 response = test_config.FakeResponse({HttpHeaders.SubStatus: SubStatusCodes.READ_SESSION_NOTAVAILABLE}) - raise errors.CosmosHttpResponseError( + raise exceptions.CosmosHttpResponseError( status_code=StatusCodes.NOT_FOUND, message="Read Session not available", response=response) @@ -134,7 +134,7 @@ def validate_retry_on_session_not_availabe(self, is_preferred_locations_list_emp try: client.ReadItem("dbs/mydb/colls/mycoll/docs/1") - except errors.CosmosHttpResponseError as e: + except exceptions.CosmosHttpResponseError as e: # not retried self.assertEqual(self.counter, 4 if use_multiple_write_locations else 2) self.counter = 0 @@ -167,7 +167,7 @@ def _MockExecuteFunctionSessionReadFailureTwice(self, function, *args, **kwargs) self.assertEqual(expected_endpoint, request.location_endpoint_to_route) self.counter += 1 response = test_config.FakeResponse({HttpHeaders.SubStatus: SubStatusCodes.READ_SESSION_NOTAVAILABLE}) - raise errors.CosmosHttpResponseError( + raise exceptions.CosmosHttpResponseError( status_code=StatusCodes.NOT_FOUND, message="Read Session not available", response=response) diff --git a/sdk/cosmos/azure-cosmos/test/query_tests.py b/sdk/cosmos/azure-cosmos/test/query_tests.py index 9fc10882ac60..ef4497728981 100644 --- a/sdk/cosmos/azure-cosmos/test/query_tests.py +++ b/sdk/cosmos/azure-cosmos/test/query_tests.py @@ -2,7 +2,13 @@ import uuid import azure.cosmos.cosmos_client as cosmos_client import azure.cosmos._retry_utility as retry_utility +from azure.cosmos._execution_context.query_execution_info import _PartitionedQueryExecutionInfo +import azure.cosmos.errors as errors +from azure.cosmos.partition_key import PartitionKey +from azure.cosmos._execution_context.base_execution_context import _QueryExecutionContextBase +from azure.cosmos.documents import _DistinctType import pytest +import collections import test_config pytestmark = pytest.mark.cosmosEmulator @@ -184,11 +190,7 @@ def test_max_item_count_honored_in_order_by_query(self): max_item_count=1, enable_cross_partition_query=True ) - # 1 call to get query plans, 1 call to get pkr, 10 calls to one partion with the documents, 1 call each to other 4 partitions - if 'localhost' in self.host or '127.0.0.1' in self.host: # TODO: Differing result between live and emulator - self.validate_query_requests_count(query_iterable, 16 * 2) - else: - self.validate_query_requests_count(query_iterable, 17 * 2) + self.validate_query_requests_count(query_iterable, 15 * 2 + 1) query_iterable = created_collection.query_items( query=query, @@ -196,8 +198,7 @@ def test_max_item_count_honored_in_order_by_query(self): enable_cross_partition_query=True ) - # 1 call to get query plan 1 calls to one partition with the documents, 1 call each to other 4 partitions - self.validate_query_requests_count(query_iterable, 6 * 2) + self.validate_query_requests_count(query_iterable, 13) def validate_query_requests_count(self, query_iterable, expected_count): self.count = 0 @@ -213,6 +214,337 @@ def _MockExecuteFunction(self, function, *args, **kwargs): self.count += 1 return self.OriginalExecuteFunction(function, *args, **kwargs) + def test_get_query_plan_through_gateway(self): + created_collection = self.config.create_multi_partition_collection_with_custom_pk_if_not_exist(self.client) + self._validate_query_plan(query="Select top 10 value count(c.id) from c", + container_link=created_collection.container_link, + top=10, + order_by=[], + aggregate=['Count'], + select_value=True, + offset=None, + limit=None, + distinct=_DistinctType.NoneType) + + self._validate_query_plan(query="Select * from c order by c._ts offset 5 limit 10", + container_link=created_collection.container_link, + top=None, + order_by=['Ascending'], + aggregate=[], + select_value=False, + offset=5, + limit=10, + distinct=_DistinctType.NoneType) + + self._validate_query_plan(query="Select distinct value c.id from c order by c.id", + container_link=created_collection.container_link, + top=None, + order_by=['Ascending'], + aggregate=[], + select_value=True, + offset=None, + limit=None, + distinct=_DistinctType.Ordered) + + def _validate_query_plan(self, query, container_link, top, order_by, aggregate, select_value, offset, limit, distinct): + query_plan_dict = self.client.client_connection._GetQueryPlanThroughGateway(query, container_link) + query_execution_info = _PartitionedQueryExecutionInfo(query_plan_dict) + self.assertTrue(query_execution_info.has_rewritten_query()) + self.assertEquals(query_execution_info.has_distinct_type(), distinct != "None") + self.assertEquals(query_execution_info.get_distinct_type(), distinct) + self.assertEquals(query_execution_info.has_top(), top is not None) + self.assertEquals(query_execution_info.get_top(), top) + self.assertEquals(query_execution_info.has_order_by(), len(order_by) > 0) + self.assertListEqual(query_execution_info.get_order_by(), order_by) + self.assertEquals(query_execution_info.has_aggregates(), len(aggregate) > 0) + self.assertListEqual(query_execution_info.get_aggregates(), aggregate) + self.assertEquals(query_execution_info.has_select_value(), select_value) + self.assertEquals(query_execution_info.has_offset(), offset is not None) + self.assertEquals(query_execution_info.get_offset(), offset) + self.assertEquals(query_execution_info.has_limit(), limit is not None) + self.assertEquals(query_execution_info.get_limit(), limit) + + def test_unsupported_queries(self): + created_collection = self.config.create_multi_partition_collection_with_custom_pk_if_not_exist(self.client) + queries = ['SELECT COUNT(1) FROM c', 'SELECT COUNT(1) + 5 FROM c', 'SELECT COUNT(1) + SUM(c) FROM c'] + for query in queries: + query_iterable = created_collection.query_items(query=query, enable_cross_partition_query=True) + try: + list(query_iterable) + self.fail() + except errors.CosmosHttpResponseError as e: + self.assertEqual(e.status_code, 400) + + def test_query_with_non_overlapping_pk_ranges(self): + created_collection = self.config.create_multi_partition_collection_with_custom_pk_if_not_exist(self.client) + query_iterable = created_collection.query_items("select * from c where c.pk='1' or c.pk='2'", enable_cross_partition_query=True) + self.assertListEqual(list(query_iterable), []) + + def test_offset_limit(self): + created_collection = self.config.create_multi_partition_collection_with_custom_pk_if_not_exist(self.client) + max_item_counts = [0, 2, 5, 10] + values = [] + for i in range(10): + document_definition = {'pk': i, 'id': 'myId' + str(uuid.uuid4())} + values.append(created_collection.create_item(body=document_definition)['pk']) + + for max_item_count in max_item_counts: + self._validate_offset_limit(created_collection=created_collection, + query='SELECT * from c ORDER BY c.pk OFFSET 0 LIMIT 5', + max_item_count=max_item_count, + results=values[:5]) + + self._validate_offset_limit(created_collection=created_collection, + query='SELECT * from c ORDER BY c.pk OFFSET 5 LIMIT 10', + max_item_count=max_item_count, + results=values[5:]) + + self._validate_offset_limit(created_collection=created_collection, + query='SELECT * from c ORDER BY c.pk OFFSET 10 LIMIT 5', + max_item_count=max_item_count, + results=[]) + + self._validate_offset_limit(created_collection=created_collection, + query='SELECT * from c ORDER BY c.pk OFFSET 100 LIMIT 1', + max_item_count=max_item_count, + results=[]) + + def _validate_offset_limit(self, created_collection, query, max_item_count, results): + query_iterable = created_collection.query_items( + query=query, + enable_cross_partition_query=True, + max_item_count=max_item_count + ) + self.assertListEqual(list(map(lambda doc: doc['pk'], list(query_iterable))), results) + + def test_distinct(self): + created_database = self.config.create_database_if_not_exist(self.client) + distinct_field = 'distinct_field' + pk_field = "pk" + different_field = "different_field" + + created_collection = created_database.create_container( + id='collection with composite index ' + str(uuid.uuid4()), + partition_key=PartitionKey(path="/pk", kind="Hash"), + indexing_policy={ + "compositeIndexes": [ + [{"path": "/" + pk_field, "order": "ascending"}, {"path": "/" + distinct_field, "order": "ascending"}], + [{"path": "/" + distinct_field, "order": "ascending"}, {"path": "/" + pk_field, "order": "ascending"}] + ] + } + ) + documents = [] + for i in range(5): + j = i + while j > i - 5: + document_definition = {pk_field: i, 'id': str(uuid.uuid4()), distinct_field: j} + documents.append(created_collection.create_item(body=document_definition)) + document_definition = {pk_field: i, 'id': str(uuid.uuid4()), distinct_field: j} + documents.append(created_collection.create_item(body=document_definition)) + document_definition = {pk_field: i, 'id': str(uuid.uuid4())} + documents.append(created_collection.create_item(body=document_definition)) + j -= 1 + + padded_docs = self._pad_with_none(documents, distinct_field) + + self._validate_distinct(created_collection=created_collection, + query='SELECT distinct c.%s from c ORDER BY c.%s' % (distinct_field, distinct_field), + results=self._get_distinct_docs(self._get_order_by_docs(padded_docs, distinct_field, None), distinct_field, None, True), + is_select=False, + fields=[distinct_field]) + + self._validate_distinct(created_collection=created_collection, + query='SELECT distinct c.%s, c.%s from c ORDER BY c.%s, c.%s' % (distinct_field, pk_field, pk_field, distinct_field), + results=self._get_distinct_docs(self._get_order_by_docs(padded_docs, pk_field, distinct_field), distinct_field, pk_field, True), + is_select=False, + fields=[distinct_field, pk_field]) + + self._validate_distinct(created_collection=created_collection, + query='SELECT distinct c.%s, c.%s from c ORDER BY c.%s, c.%s' % (distinct_field, pk_field, distinct_field, pk_field), + results=self._get_distinct_docs(self._get_order_by_docs(padded_docs, distinct_field, pk_field), distinct_field, pk_field, True), + is_select=False, + fields=[distinct_field, pk_field]) + + self._validate_distinct(created_collection=created_collection, + query='SELECT distinct value c.%s from c ORDER BY c.%s' % (distinct_field, distinct_field), + results=self._get_distinct_docs(self._get_order_by_docs(padded_docs, distinct_field, None), distinct_field, None, True), + is_select=False, + fields=[distinct_field]) + + self._validate_distinct(created_collection=created_collection, + query='SELECT distinct c.%s from c' % (distinct_field), + results=self._get_distinct_docs(padded_docs, distinct_field, None, False), + is_select=True, + fields=[distinct_field]) + + self._validate_distinct(created_collection=created_collection, + query='SELECT distinct c.%s, c.%s from c' % (distinct_field, pk_field), + results=self._get_distinct_docs(padded_docs, distinct_field, pk_field, False), + is_select=True, + fields=[distinct_field, pk_field]) + + self._validate_distinct(created_collection=created_collection, + query='SELECT distinct value c.%s from c' % (distinct_field), + results=self._get_distinct_docs(padded_docs, distinct_field, None, True), + is_select=True, + fields=[distinct_field]) + + self._validate_distinct(created_collection=created_collection, + query='SELECT distinct c.%s from c ORDER BY c.%s' % (different_field, different_field), + results=[], + is_select=True, + fields=[different_field]) + + self._validate_distinct(created_collection=created_collection, + query='SELECT distinct c.%s from c' % (different_field), + results=['None'], + is_select=True, + fields=[different_field]) + + created_database.delete_container(created_collection.id) + + def _get_order_by_docs(self, documents, field1, field2): + if field2 is None: + return sorted(documents, key=lambda d: (d[field1] is not None, d[field1])) + else: + return sorted(documents, key=lambda d: (d[field1] is not None, d[field1], d[field2] is not None, d[field2])) + + def _get_distinct_docs(self, documents, field1, field2, is_order_by_or_value): + if field2 is None: + res = collections.OrderedDict.fromkeys(doc[field1] for doc in documents) + if is_order_by_or_value: + res = filter(lambda x: False if x is None else True, res) + else: + res = collections.OrderedDict.fromkeys(str(doc[field1]) + "," + str(doc[field2]) for doc in documents) + return list(res) + + def _pad_with_none(self, documents, field): + for doc in documents: + if field not in doc: + doc[field] = None + return documents + + def _validate_distinct(self, created_collection, query, results, is_select, fields): + query_iterable = created_collection.query_items( + query=query, + enable_cross_partition_query=True + ) + query_results = list(query_iterable) + + self.assertEquals(len(results), len(query_results)) + query_results_strings = [] + result_strings = [] + for i in range(len(results)): + query_results_strings.append(self._get_query_result_string(query_results[i], fields)) + result_strings.append(str(results[i])) + if is_select: + query_results_strings = sorted(query_results_strings) + result_strings = sorted(result_strings) + self.assertListEqual(result_strings, query_results_strings) + + def _get_query_result_string(self, query_result, fields): + if type(query_result) is not dict: + return str(query_result) + res = str(query_result[fields[0]] if fields[0] in query_result else None) + if len(fields) == 2: + res = res + "," + str(query_result[fields[1]] if fields[1] in query_result else None) + + return res + + def test_distinct_on_different_types_and_field_orders(self): + created_collection = self.config.create_multi_partition_collection_with_custom_pk_if_not_exist(self.client) + self.payloads = [ + {'f1': 1, 'f2': 'value', 'f3': 100000000000000000, 'f4': [1, 2, '3'], 'f5': {'f6': {'f7': 2}}}, + {'f2': '\'value', 'f4': [1.0, 2, '3'], 'f5': {'f6': {'f7': 2.0}}, 'f1': 1.0, 'f3': 100000000000000000.00}, + {'f3': 100000000000000000.0, 'f5': {'f6': {'f7': 2}}, 'f2': '\'value', 'f1': 1, 'f4': [1, 2.0, '3']} + ] + self.OriginalExecuteFunction = _QueryExecutionContextBase.next + _QueryExecutionContextBase.next = self._MockNextFunction + + self._validate_distinct_on_different_types_and_field_orders( + collection=created_collection, + query="Select distinct value c.f1 from c", + expected_results=[1], + get_mock_result=lambda x, i: (None, x[i]["f1"]) + ) + + self._validate_distinct_on_different_types_and_field_orders( + collection=created_collection, + query="Select distinct value c.f2 from c", + expected_results=['value', '\'value'], + get_mock_result=lambda x, i: (None, x[i]["f2"]) + ) + + self._validate_distinct_on_different_types_and_field_orders( + collection=created_collection, + query="Select distinct value c.f2 from c order by c.f2", + expected_results=['\'value', 'value'], + get_mock_result=lambda x, i: (x[i]["f2"], x[i]["f2"]) + ) + + self._validate_distinct_on_different_types_and_field_orders( + collection=created_collection, + query="Select distinct value c.f3 from c", + expected_results=[100000000000000000], + get_mock_result=lambda x, i: (None, x[i]["f3"]) + ) + + self._validate_distinct_on_different_types_and_field_orders( + collection=created_collection, + query="Select distinct value c.f4 from c", + expected_results=[[1, 2, '3']], + get_mock_result=lambda x, i: (None, x[i]["f4"]) + ) + + self._validate_distinct_on_different_types_and_field_orders( + collection=created_collection, + query="Select distinct value c.f5.f6 from c", + expected_results=[{'f7': 2}], + get_mock_result=lambda x, i: (None, x[i]["f5"]["f6"]) + ) + + self._validate_distinct_on_different_types_and_field_orders( + collection=created_collection, + query="Select distinct c.f1, c.f2, c.f3 from c", + expected_results=[self.payloads[0], self.payloads[1]], + get_mock_result=lambda x, i: (None, x[i]) + ) + + self._validate_distinct_on_different_types_and_field_orders( + collection=created_collection, + query="Select distinct c.f1, c.f2, c.f3 from c order by c.f1", + expected_results=[self.payloads[0], self.payloads[1]], + get_mock_result=lambda x, i: (i, x[i]) + ) + + _QueryExecutionContextBase.next = self.OriginalExecuteFunction + + def _validate_distinct_on_different_types_and_field_orders(self, collection, query, expected_results, get_mock_result): + self.count = 0 + self.get_mock_result = get_mock_result + query_iterable = collection.query_items(query, enable_cross_partition_query=True) + results = list(query_iterable) + for i in range(len(expected_results)): + if isinstance(results[i], dict): + self.assertDictEqual(results[i], expected_results[i]) + elif isinstance(results[i], list): + self.assertListEqual(results[i], expected_results[i]) + else: + self.assertEquals(results[i], expected_results[i]) + self.count = 0 + + def _MockNextFunction(self): + if self.count < len(self.payloads): + item, result = self.get_mock_result(self.payloads, self.count) + self.count += 1 + if item is not None: + return {'orderByItems': [{'item': item}], '_rid': 'fake_rid', 'payload': result} + else: + return result + return result + else: + raise StopIteration + if __name__ == "__main__": unittest.main() \ No newline at end of file diff --git a/sdk/cosmos/azure-cosmos/test/retry_policy_tests.py b/sdk/cosmos/azure-cosmos/test/retry_policy_tests.py index 3523f9b6489e..e6e73698dec9 100644 --- a/sdk/cosmos/azure-cosmos/test/retry_policy_tests.py +++ b/sdk/cosmos/azure-cosmos/test/retry_policy_tests.py @@ -24,7 +24,7 @@ import azure.cosmos.cosmos_client as cosmos_client import pytest import azure.cosmos.documents as documents -import azure.cosmos.errors as errors +import azure.cosmos.exceptions as exceptions import azure.cosmos._retry_options as retry_options from azure.cosmos.http_constants import HttpHeaders, StatusCodes, SubStatusCodes from azure.cosmos import _retry_utility @@ -58,7 +58,7 @@ def __AssertHTTPFailureWithStatus(self, status_code, func, *args, **kwargs): try: func(*args, **kwargs) self.assertFalse(True, 'function should fail.') - except errors.CosmosHttpResponseError as inst: + except exceptions.CosmosHttpResponseError as inst: self.assertEqual(inst.status_code, status_code) @classmethod @@ -88,7 +88,7 @@ def test_resource_throttle_retry_policy_default_retry_after(self): try: self.created_collection.create_item(body=document_definition) - except errors.CosmosHttpResponseError as e: + except exceptions.CosmosHttpResponseError as e: self.assertEqual(e.status_code, StatusCodes.TOO_MANY_REQUESTS) self.assertEqual(connection_policy.RetryOptions.MaxRetryAttemptCount, self.created_collection.client_connection.last_response_headers[HttpHeaders.ThrottleRetryCount]) self.assertGreaterEqual( self.created_collection.client_connection.last_response_headers[HttpHeaders.ThrottleRetryWaitTimeInMs], @@ -110,7 +110,7 @@ def test_resource_throttle_retry_policy_fixed_retry_after(self): try: self.created_collection.create_item(body=document_definition) - except errors.CosmosHttpResponseError as e: + except exceptions.CosmosHttpResponseError as e: self.assertEqual(e.status_code, StatusCodes.TOO_MANY_REQUESTS) self.assertEqual(connection_policy.RetryOptions.MaxRetryAttemptCount, self.created_collection.client_connection.last_response_headers[HttpHeaders.ThrottleRetryCount]) self.assertGreaterEqual(self.created_collection.client_connection.last_response_headers[HttpHeaders.ThrottleRetryWaitTimeInMs], @@ -133,7 +133,7 @@ def test_resource_throttle_retry_policy_max_wait_time(self): try: self.created_collection.create_item(body=document_definition) - except errors.CosmosHttpResponseError as e: + except exceptions.CosmosHttpResponseError as e: self.assertEqual(e.status_code, StatusCodes.TOO_MANY_REQUESTS) self.assertGreaterEqual(self.created_collection.client_connection.last_response_headers[HttpHeaders.ThrottleRetryWaitTimeInMs], connection_policy.RetryOptions.MaxWaitTimeInSeconds * 1000) @@ -162,7 +162,7 @@ def test_resource_throttle_retry_policy_query(self): { 'name':'@id', 'value':document_definition['id'] } ] })) - except errors.CosmosHttpResponseError as e: + except exceptions.CosmosHttpResponseError as e: self.assertEqual(e.status_code, StatusCodes.TOO_MANY_REQUESTS) self.assertEqual(connection_policy.RetryOptions.MaxRetryAttemptCount, self.created_collection.client_connection.last_response_headers[HttpHeaders.ThrottleRetryCount]) @@ -238,7 +238,7 @@ def test_default_retry_policy_for_create(self): created_document = {} try : created_document = self.created_collection.create_item(body=document_definition) - except errors.CosmosHttpResponseError as err: + except exceptions.CosmosHttpResponseError as err: self.assertEqual(err.status_code, 10054) self.assertDictEqual(created_document, {}) @@ -250,7 +250,7 @@ def test_default_retry_policy_for_create(self): def _MockExecuteFunction(self, function, *args, **kwargs): response = test_config.FakeResponse({HttpHeaders.RetryAfterInMilliseconds: self.retry_after_in_milliseconds}) - raise errors.CosmosHttpResponseError( + raise exceptions.CosmosHttpResponseError( status_code=StatusCodes.TOO_MANY_REQUESTS, message="Request rate is too large", response=response) @@ -267,7 +267,7 @@ def __call__(self, func, *args, **kwargs): if self.counter % 3 == 0: return self.org_func(func, *args, **kwargs) else: - raise errors.CosmosHttpResponseError( + raise exceptions.CosmosHttpResponseError( status_code=10054, message="Connection was reset", response=test_config.FakeResponse({})) diff --git a/sdk/cosmos/azure-cosmos/test/session_tests.py b/sdk/cosmos/azure-cosmos/test/session_tests.py index c74506248f6f..85ef48ae531a 100644 --- a/sdk/cosmos/azure-cosmos/test/session_tests.py +++ b/sdk/cosmos/azure-cosmos/test/session_tests.py @@ -7,7 +7,7 @@ import azure.cosmos.cosmos_client as cosmos_client import azure.cosmos.documents as documents import test_config -import azure.cosmos.errors as errors +import azure.cosmos.exceptions as exceptions from azure.cosmos.http_constants import StatusCodes, SubStatusCodes, HttpHeaders import azure.cosmos._synchronized_request as synchronized_request from azure.cosmos import _retry_utility @@ -58,7 +58,7 @@ def test_session_token_not_sent_for_master_resource_ops (self): def _MockExecuteFunctionSessionReadFailureOnce(self, function, *args, **kwargs): response = test_config.FakeResponse({HttpHeaders.SubStatus: SubStatusCodes.READ_SESSION_NOTAVAILABLE}) - raise errors.CosmosHttpResponseError( + raise exceptions.CosmosHttpResponseError( status_code=StatusCodes.NOT_FOUND, message="Read Session not available", response=response) @@ -70,7 +70,7 @@ def test_clear_session_token(self): _retry_utility.ExecuteFunction = self._MockExecuteFunctionSessionReadFailureOnce try: self.created_collection.read_item(item=created_document['id'], partition_key='mypk') - except errors.CosmosHttpResponseError as e: + except exceptions.CosmosHttpResponseError as e: self.assertEqual(self.client.client_connection.session.get_session_token( 'dbs/' + self.created_db.id + '/colls/' + self.created_collection.id), "") self.assertEqual(e.status_code, StatusCodes.NOT_FOUND) @@ -88,7 +88,7 @@ def test_internal_server_error_raised_for_invalid_session_token_received_from_se try: self.created_collection.create_item(body={'id': '1' + str(uuid.uuid4()), 'pk': 'mypk'}) self.fail() - except errors.CosmosHttpResponseError as e: + except exceptions.CosmosHttpResponseError as e: self.assertEqual(e.http_error_message, "Could not parse the received session token: 2") self.assertEqual(e.status_code, StatusCodes.INTERNAL_SERVER_ERROR) _retry_utility.ExecuteFunction = self.OriginalExecuteFunction diff --git a/sdk/cosmos/azure-cosmos/test/session_token_unit_tests.py b/sdk/cosmos/azure-cosmos/test/session_token_unit_tests.py index 91fec45ade57..f9bdbd8f3bae 100644 --- a/sdk/cosmos/azure-cosmos/test/session_token_unit_tests.py +++ b/sdk/cosmos/azure-cosmos/test/session_token_unit_tests.py @@ -1,7 +1,7 @@ import unittest import pytest from azure.cosmos._vector_session_token import VectorSessionToken -from azure.cosmos.errors import CosmosHttpResponseError +from azure.cosmos.exceptions import CosmosHttpResponseError pytestmark = pytest.mark.cosmosEmulator diff --git a/sdk/cosmos/azure-cosmos/test/streaming_failover_test.py b/sdk/cosmos/azure-cosmos/test/streaming_failover_test.py index 2f92dd8a92f5..e984199609da 100644 --- a/sdk/cosmos/azure-cosmos/test/streaming_failover_test.py +++ b/sdk/cosmos/azure-cosmos/test/streaming_failover_test.py @@ -2,7 +2,7 @@ import azure.cosmos._cosmos_client_connection as cosmos_client_connection import pytest import azure.cosmos.documents as documents -import azure.cosmos.errors as errors +import azure.cosmos.exceptions as exceptions import test_config from azure.cosmos.http_constants import HttpHeaders, StatusCodes, SubStatusCodes from azure.cosmos import _retry_utility @@ -85,7 +85,7 @@ def _MockExecuteFunctionEndpointDiscover(self, function, *args, **kwargs): else: self.endpoint_sequence.append(args[1].location_endpoint_to_route) response = test_config.FakeResponse({HttpHeaders.SubStatus: SubStatusCodes.WRITE_FORBIDDEN}) - raise errors.CosmosHttpResponseError( + raise exceptions.CosmosHttpResponseError( status_code=StatusCodes.FORBIDDEN, message="Request is not permitted in this region", response=response) @@ -112,7 +112,7 @@ def test_retry_policy_does_not_mark_null_locations_unavailable(self): self._write_counter = 0 request = RequestObject(http_constants.ResourceType.Document, documents._OperationType.Read) endpointDiscovery_retry_policy = _endpoint_discovery_retry_policy.EndpointDiscoveryRetryPolicy(documents.ConnectionPolicy(), endpoint_manager, request) - endpointDiscovery_retry_policy.ShouldRetry(errors.CosmosHttpResponseError( + endpointDiscovery_retry_policy.ShouldRetry(exceptions.CosmosHttpResponseError( status_code=http_constants.StatusCodes.FORBIDDEN)) self.assertEqual(self._read_counter, 0) self.assertEqual(self._write_counter, 0) @@ -121,7 +121,7 @@ def test_retry_policy_does_not_mark_null_locations_unavailable(self): self._write_counter = 0 request = RequestObject(http_constants.ResourceType.Document, documents._OperationType.Create) endpointDiscovery_retry_policy = _endpoint_discovery_retry_policy.EndpointDiscoveryRetryPolicy(documents.ConnectionPolicy(), endpoint_manager, request) - endpointDiscovery_retry_policy.ShouldRetry(errors.CosmosHttpResponseError( + endpointDiscovery_retry_policy.ShouldRetry(exceptions.CosmosHttpResponseError( status_code=http_constants.StatusCodes.FORBIDDEN)) self.assertEqual(self._read_counter, 0) self.assertEqual(self._write_counter, 0) diff --git a/sdk/cosmos/azure-cosmos/test/test_config.py b/sdk/cosmos/azure-cosmos/test/test_config.py index 82dcafff145a..832bc609fd15 100644 --- a/sdk/cosmos/azure-cosmos/test/test_config.py +++ b/sdk/cosmos/azure-cosmos/test/test_config.py @@ -23,7 +23,7 @@ import time import uuid import azure.cosmos.documents as documents -import azure.cosmos.errors as errors +import azure.cosmos.exceptions as exceptions from azure.cosmos.http_constants import StatusCodes from azure.cosmos.database import DatabaseProxy from azure.cosmos.cosmos_client import CosmosClient @@ -87,7 +87,7 @@ def try_delete_database(cls, client): # type: (CosmosClient) -> None try: client.delete_database(cls.TEST_DATABASE_ID) - except errors.CosmosHttpResponseError as e: + except exceptions.CosmosHttpResponseError as e: if e.status_code != StatusCodes.NOT_FOUND: raise e @@ -162,7 +162,7 @@ def remove_all_documents(cls, document_collection, use_custom_partition_key): # sleep to ensure deletes are propagated for multimaster enabled accounts time.sleep(2) break - except errors.CosmosHttpResponseError as e: + except exceptions.CosmosHttpResponseError as e: print("Error occurred while deleting documents:" + str(e) + " \nRetrying...") diff --git a/sdk/cosmos/azure-cosmos/test/ttl_tests.py b/sdk/cosmos/azure-cosmos/test/ttl_tests.py index d14a189bf08b..699cae76be56 100644 --- a/sdk/cosmos/azure-cosmos/test/ttl_tests.py +++ b/sdk/cosmos/azure-cosmos/test/ttl_tests.py @@ -25,7 +25,7 @@ import pytest import azure.cosmos.cosmos_client as cosmos_client -import azure.cosmos.errors as errors +import azure.cosmos.exceptions as exceptions from azure.cosmos.http_constants import StatusCodes import test_config from azure.cosmos.partition_key import PartitionKey @@ -60,7 +60,7 @@ def __AssertHTTPFailureWithStatus(self, status_code, func, *args, **kwargs): try: func(*args, **kwargs) self.assertFalse(True, 'function should fail.') - except errors.CosmosHttpResponseError as inst: + except exceptions.CosmosHttpResponseError as inst: self.assertEqual(inst.status_code, status_code) @classmethod