Repository: Azure/azure-sdk-for-python
Pull request #40905: Enable Request Level Excluded Locations to Metadata API calls
Author: login: allenkim0129
Labels: name: Cosmos
Created: 2025-05-06T00:52:46Z | Merged: 2025-05-27T16:49:11Z

# Description

Request level excluded locations were not available for nested metadata calls. This could cause potential latency issue with high demend locations. With this changes metadata calls will also be re-routed to non-excluded locations.

### Changes
- Metadata API calls will now include request level excluded locations
- For metadata calls, all excluded locations in preferred locations will be re-ordered to the end of the available locations. This means, even thought some locations were excluded, if all non-excluded locations were not available, the excluded locations will be used during API calls
- All tests were updated to work with the changes.

# All SDK Contribution checklist:
- [x] **The pull request does not introduce [breaking changes]**
- [x] **CHANGELOG is updated for new features, bug fixes or other significant changes.**
- [x] **I have read the [contribution guidelines](https://github.com/Azure/azure-sdk-for-python/blob/main/CONTRIBUTING.md).**

## General Guidelines and Best Practices
- [x] Title of the pull request is clear and informative.
- [ ] There are a small number of commits, each of which have an informative message. This means that previously merged commits do not appear in the history of the PR. For more information on cleaning up the commits in your PR, [see this page](https://github.com/Azure/azure-powershell/blob/master/documentation/development-docs/cleaning-up-commits.md).

### [Testing Guidelines](https://github.com/Azure/azure-sdk-for-python/blob/main/CONTRIBUTING.md##building-and-testing)
- [ ] Pull request includes test coverage for the included changes.



Diff:
diff --git a/sdk/cosmos/azure-cosmos/CHANGELOG.md b/sdk/cosmos/azure-cosmos/CHANGELOG.md
index 4004e31233be..c6f7ad8f7b42 100644
--- a/sdk/cosmos/azure-cosmos/CHANGELOG.md
+++ b/sdk/cosmos/azure-cosmos/CHANGELOG.md
@@ -1,6 +1,7 @@
 ## Release History
 
 ### 4.12.0b2 (Unreleased)
+* Added ability to use request level `excluded_locations` on metadata calls, such as getting container properties. See [PR 40905](https://github.com/Azure/azure-sdk-for-python/pull/40905)
 
 #### Features Added
 
diff --git a/sdk/cosmos/azure-cosmos/azure/cosmos/_change_feed/aio/change_feed_fetcher.py b/sdk/cosmos/azure-cosmos/azure/cosmos/_change_feed/aio/change_feed_fetcher.py
index d997360e4c41..7a49b89979cb 100644
--- a/sdk/cosmos/azure-cosmos/azure/cosmos/_change_feed/aio/change_feed_fetcher.py
+++ b/sdk/cosmos/azure-cosmos/azure/cosmos/_change_feed/aio/change_feed_fetcher.py
@@ -145,9 +145,11 @@ async def callback():
         except CosmosHttpResponseError as e:
             if exceptions._partition_range_is_gone(e) or exceptions._is_partition_split_or_merge(e):
                 # refresh change feed state
-                await self._change_feed_state.handle_feed_range_gone_async(
-                    self._client._routing_map_provider,
-                    self._resource_link)
+                options = None
+                if "excludedLocations" in self._feed_options:
+                    options = {'excludedLocations': self._feed_options["excludedLocations"]}
+                await self._change_feed_state.handle_feed_range_gone_async(self._client._routing_map_provider,
+                    self._resource_link, options)
             else:
                 raise e
 
diff --git a/sdk/cosmos/azure-cosmos/azure/cosmos/_change_feed/change_feed_fetcher.py b/sdk/cosmos/azure-cosmos/azure/cosmos/_change_feed/change_feed_fetcher.py
index c3ff6472af28..4b14a514e7e6 100644
--- a/sdk/cosmos/azure-cosmos/azure/cosmos/_change_feed/change_feed_fetcher.py
+++ b/sdk/cosmos/azure-cosmos/azure/cosmos/_change_feed/change_feed_fetcher.py
@@ -140,7 +140,11 @@ def callback():
         except CosmosHttpResponseError as e:
             if exceptions._partition_range_is_gone(e) or exceptions._is_partition_split_or_merge(e):
                 # refresh change feed state
-                self._change_feed_state.handle_feed_range_gone(self._client._routing_map_provider, self._resource_link)
+                options = None
+                if "excludedLocations" in self._feed_options:
+                    options = {'excludedLocations': self._feed_options["excludedLocations"]}
+                self._change_feed_state.handle_feed_range_gone(self._client._routing_map_provider, self._resource_link,
+                                                               options)
             else:
                 raise e
 
diff --git a/sdk/cosmos/azure-cosmos/azure/cosmos/_change_feed/change_feed_state.py b/sdk/cosmos/azure-cosmos/azure/cosmos/_change_feed/change_feed_state.py
index 1d5bfd3baf64..b1fc5ed3d205 100644
--- a/sdk/cosmos/azure-cosmos/azure/cosmos/_change_feed/change_feed_state.py
+++ b/sdk/cosmos/azure-cosmos/azure/cosmos/_change_feed/change_feed_state.py
@@ -64,14 +64,16 @@ def populate_feed_options(self, feed_options: Dict[str, Any]) -> None:
     def populate_request_headers(
             self,
             routing_provider: SmartRoutingMapProvider,
-            request_headers: Dict[str, Any]) -> None:
+            request_headers: Dict[str, Any],
+            feed_options: Optional[Dict[str, Any]] = None) -> None:
         pass
 
     @abstractmethod
     async def populate_request_headers_async(
             self,
             async_routing_provider: AsyncSmartRoutingMapProvider,
-            request_headers: Dict[str, Any]) -> None:
+            request_headers: Dict[str, Any],
+            feed_options: Optional[Dict[str, Any]] = None) -> None:
         pass
 
     @abstractmethod
@@ -149,7 +151,8 @@ def from_json(
     def populate_request_headers(
             self,
             routing_provider: SmartRoutingMapProvider,
-            request_headers: Dict[str, Any]) -> None:
+            request_headers: Dict[str, Any],
+            feed_options: Optional[Dict[str, Any]] = None) -> None:
         request_headers[http_constants.HttpHeaders.AIM] = http_constants.HttpHeaders.IncrementalFeedHeaderValue
 
         self._change_feed_start_from.populate_request_headers(request_headers)
@@ -159,7 +162,8 @@ def populate_request_headers(
     async def populate_request_headers_async(
             self,
             async_routing_provider: AsyncSmartRoutingMapProvider,
-            request_headers: Dict[str, Any]) -> None: # pylint: disable=unused-argument
+            request_headers: Dict[str, Any],
+            feed_options: Optional[Dict[str, Any]] = None) -> None: # pylint: disable=unused-argument
 
         request_headers[http_constants.HttpHeaders.AIM] = http_constants.HttpHeaders.IncrementalFeedHeaderValue
 
@@ -277,14 +281,16 @@ def set_mode_request_headers(
     def populate_request_headers(
             self,
             routing_provider: SmartRoutingMapProvider,
-            request_headers: Dict[str, Any]) -> None:
+            request_headers: Dict[str, Any],
+            feed_options = None) -> None:
         self.set_start_from_request_headers(request_headers)
 
         # based on the feed range to find the overlapping partition key range id
         over_lapping_ranges = \
             routing_provider.get_overlapping_ranges(
                 self._container_link,
-                [self._continuation.current_token.feed_range])
+                [self._continuation.current_token.feed_range],
+                feed_options)
 
         self.set_pk_range_id_request_headers(over_lapping_ranges, request_headers)
 
@@ -294,14 +300,16 @@ def populate_request_headers(
     async def populate_request_headers_async(
             self,
             async_routing_provider: AsyncSmartRoutingMapProvider,
-            request_headers: Dict[str, Any]) -> None:
+            request_headers: Dict[str, Any],
+            feed_options: Optional[Dict[str, Any]] = None) -> None:
         self.set_start_from_request_headers(request_headers)
 
         # based on the feed range to find the overlapping partition key range id
         over_lapping_ranges = \
             await async_routing_provider.get_overlapping_ranges(
                 self._container_link,
-                [self._continuation.current_token.feed_range])
+                [self._continuation.current_token.feed_range],
+                feed_options)
 
         self.set_pk_range_id_request_headers(over_lapping_ranges, request_headers)
 
@@ -313,14 +321,16 @@ def populate_feed_options(self, feed_options: Dict[str, Any]) -> None:
     def handle_feed_range_gone(
             self,
             routing_provider: SmartRoutingMapProvider,
-            resource_link: str) -> None:
-        self._continuation.handle_feed_range_gone(routing_provider, resource_link)
+            resource_link: str,
+            feed_options: Optional[Dict[str, Any]] = None) -> None:
+        self._continuation.handle_feed_range_gone(routing_provider, resource_link, feed_options)
 
     async def handle_feed_range_gone_async(
             self,
             routing_provider: AsyncSmartRoutingMapProvider,
-            resource_link: str) -> None:
-        await self._continuation.handle_feed_range_gone_async(routing_provider, resource_link)
+            resource_link: str,
+            feed_options: Optional[Dict[str, Any]] = None) -> None:
+        await self._continuation.handle_feed_range_gone_async(routing_provider, resource_link, feed_options)
 
     def apply_server_response_continuation(self, continuation: str, has_modified_response: bool) -> None:
         self._continuation.apply_server_response_continuation(continuation, has_modified_response)
diff --git a/sdk/cosmos/azure-cosmos/azure/cosmos/_change_feed/feed_range_composite_continuation_token.py b/sdk/cosmos/azure-cosmos/azure/cosmos/_change_feed/feed_range_composite_continuation_token.py
index 8f87ccfa194a..3afe80bbbf2b 100644
--- a/sdk/cosmos/azure-cosmos/azure/cosmos/_change_feed/feed_range_composite_continuation_token.py
+++ b/sdk/cosmos/azure-cosmos/azure/cosmos/_change_feed/feed_range_composite_continuation_token.py
@@ -101,8 +101,10 @@ def from_json(cls, data) -> 'FeedRangeCompositeContinuation':
     def handle_feed_range_gone(
             self,
             routing_provider: SmartRoutingMapProvider,
-            collection_link: str) -> None:
-        overlapping_ranges = routing_provider.get_overlapping_ranges(collection_link, [self._current_token.feed_range])
+            collection_link: str,
+            feed_options: Optional[Dict[str, Any]] = None) -> None:
+        overlapping_ranges = routing_provider.get_overlapping_ranges(collection_link,
+                                                                     [self._current_token.feed_range], feed_options)
 
         if len(overlapping_ranges) == 1:
             # merge,reusing the existing the feedRange and continuationToken
@@ -122,11 +124,13 @@ def handle_feed_range_gone(
     async def handle_feed_range_gone_async(
             self,
             routing_provider: AsyncSmartRoutingMapProvider,
-            collection_link: str) -> None:
+            collection_link: str,
+            feed_options: Optional[Dict[str, Any]] = None) -> None:
         overlapping_ranges = \
             await routing_provider.get_overlapping_ranges(
                 collection_link,
-                [self._current_token.feed_range])
+                [self._current_token.feed_range],
+                feed_options)
 
         if len(overlapping_ranges) == 1:
             # merge,reusing the existing the feedRange and continuationToken
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 69fd2cf53440..facc493c4794 100644
--- a/sdk/cosmos/azure-cosmos/azure/cosmos/_cosmos_client_connection.py
+++ b/sdk/cosmos/azure-cosmos/azure/cosmos/_cosmos_client_connection.py
@@ -378,7 +378,7 @@ def CreateDatabase(
             options = {}
         base._validate_resource(database)
         path = "/dbs"
-        return self.Create(database, path, "dbs", None, None, options, **kwargs)
+        return self.Create(database, path, http_constants.ResourceType.Database, None, None, options, **kwargs)
 
     def ReadDatabase(
         self,
@@ -403,7 +403,7 @@ def ReadDatabase(
 
         path = base.GetPathFromLink(database_link)
         database_id = base.GetResourceIdOrFullNameFromLink(database_link)
-        return self.Read(path, "dbs", database_id, None, options, **kwargs)
+        return self.Read(path, http_constants.ResourceType.Database, database_id, None, options, **kwargs)
 
     def ReadDatabases(
         self,
@@ -448,7 +448,7 @@ def QueryDatabases(
 
         def fetch_fn(options: Mapping[str, Any]) -> Tuple[List[Dict[str, Any]], CaseInsensitiveDict]:
             return self.__QueryFeed(
-                    "/dbs", "dbs", "", lambda r: r["Databases"],
+                    "/dbs", http_constants.ResourceType.Database, "", lambda r: r["Databases"],
                     lambda _, b: b, query, options, **kwargs)
 
         return ItemPaged(
@@ -501,12 +501,12 @@ def QueryContainers(
         if options is None:
             options = {}
 
-        path = base.GetPathFromLink(database_link, "colls")
+        path = base.GetPathFromLink(database_link, http_constants.ResourceType.Collection)
         database_id = base.GetResourceIdOrFullNameFromLink(database_link)
 
         def fetch_fn(options: Mapping[str, Any]) -> Tuple[List[Dict[str, Any]], CaseInsensitiveDict]:
             return self.__QueryFeed(
-                    path, "colls", database_id, lambda r: r["DocumentCollections"],
+                    path, http_constants.ResourceType.Collection, database_id, lambda r: r["DocumentCollections"],
                     lambda _, body: body, query, options, **kwargs)
 
         return ItemPaged(
@@ -537,9 +537,10 @@ def CreateContainer(
             options = {}
 
         base._validate_resource(collection)
-        path = base.GetPathFromLink(database_link, "colls")
+        path = base.GetPathFromLink(database_link, http_constants.ResourceType.Collection)
         database_id = base.GetResourceIdOrFullNameFromLink(database_link)
-        return self.Create(collection, path, "colls", database_id, None, options, **kwargs)
+        return self.Create(collection, path, http_constants.ResourceType.Collection, database_id, None,
+                           options, **kwargs)
 
     def ReplaceContainer(
         self,
@@ -569,7 +570,8 @@ def ReplaceContainer(
         base._validate_resource(collection)
         path = base.GetPathFromLink(collection_link)
         collection_id = base.GetResourceIdOrFullNameFromLink(collection_link)
-        return self.Replace(collection, path, "colls", collection_id, None, options, **kwargs)
+        return self.Replace(collection, path, http_constants.ResourceType.Collection, collection_id, None,
+                            options, **kwargs)
 
     def ReadContainer(
         self,
@@ -595,7 +597,7 @@ def ReadContainer(
 
         path = base.GetPathFromLink(collection_link)
         collection_id = base.GetResourceIdOrFullNameFromLink(collection_link)
-        return self.Read(path, "colls", collection_id, None, options, **kwargs)
+        return self.Read(path, http_constants.ResourceType.Collection, collection_id, None, options, **kwargs)
 
     def CreateUser(
         self,
@@ -623,7 +625,7 @@ def CreateUser(
             options = {}
 
         database_id, path = self._GetDatabaseIdWithPathForUser(database_link, user)
-        return self.Create(user, path, "users", database_id, None, options, **kwargs)
+        return self.Create(user, path, http_constants.ResourceType.User, database_id, None, options, **kwargs)
 
     def UpsertUser(
         self,
@@ -649,11 +651,11 @@ def UpsertUser(
             options = {}
 
         database_id, path = self._GetDatabaseIdWithPathForUser(database_link, user)
-        return self.Upsert(user, path, "users", database_id, None, options, **kwargs)
+        return self.Upsert(user, path, http_constants.ResourceType.User, database_id, None, options, **kwargs)
 
     def _GetDatabaseIdWithPathForUser(self, database_link: str, user: Mapping[str, Any]) -> Tuple[Optional[str], str]:
         base._validate_resource(user)
-        path = base.GetPathFromLink(database_link, "users")
+        path = base.GetPathFromLink(database_link, http_constants.ResourceType.User)
         database_id = base.GetResourceIdOrFullNameFromLink(database_link)
         return database_id, path
 
@@ -681,7 +683,7 @@ def ReadUser(
 
         path = base.GetPathFromLink(user_link)
         user_id = base.GetResourceIdOrFullNameFromLink(user_link)
-        return self.Read(path, "users", user_id, None, options, **kwargs)
+        return self.Read(path, http_constants.ResourceType.User, user_id, None, options, **kwargs)
 
     def ReadUsers(
         self,
@@ -730,12 +732,12 @@ def QueryUsers(
         if options is None:
             options = {}
 
-        path = base.GetPathFromLink(database_link, "users")
+        path = base.GetPathFromLink(database_link, http_constants.ResourceType.User)
         database_id = base.GetResourceIdOrFullNameFromLink(database_link)
 
         def fetch_fn(options: Mapping[str, Any]) -> Tuple[List[Dict[str, Any]], CaseInsensitiveDict]:
             return self.__QueryFeed(
-                    path, "users", database_id, lambda r: r["Users"],
+                    path, http_constants.ResourceType.User, database_id, lambda r: r["Users"],
                     lambda _, b: b, query, options, **kwargs)
 
         return ItemPaged(
@@ -766,7 +768,7 @@ def DeleteDatabase(
 
         path = base.GetPathFromLink(database_link)
         database_id = base.GetResourceIdOrFullNameFromLink(database_link)
-        self.DeleteResource(path, "dbs", database_id, None, options, **kwargs)
+        self.DeleteResource(path, http_constants.ResourceType.Database, database_id, None, options, **kwargs)
 
     def CreatePermission(
         self,
@@ -794,7 +796,7 @@ def CreatePermission(
             options = {}
 
         path, user_id = self._GetUserIdWithPathForPermission(permission, user_link)
-        return self.Create(permission, path, "permissions", user_id, None, options, **kwargs)
+        return self.Create(permission, path, http_constants.ResourceType.Permission, user_id, None, options, **kwargs)
 
     def UpsertPermission(
         self,
@@ -822,7 +824,7 @@ def UpsertPermission(
             options = {}
 
         path, user_id = self._GetUserIdWithPathForPermission(permission, user_link)
-        return self.Upsert(permission, path, "permissions", user_id, None, options, **kwargs)
+        return self.Upsert(permission, path, http_constants.ResourceType.Permission, user_id, None, options, **kwargs)
 
     def _GetUserIdWithPathForPermission(
         self,
@@ -830,7 +832,7 @@ def _GetUserIdWithPathForPermission(
         user_link: str
     ) -> Tuple[str, Optional[str]]:
         base._validate_resource(permission)
-        path = base.GetPathFromLink(user_link, "permissions")
+        path = base.GetPathFromLink(user_link, http_constants.ResourceType.Permission)
         user_id = base.GetResourceIdOrFullNameFromLink(user_link)
         return path, user_id
 
@@ -858,7 +860,7 @@ def ReadPermission(
 
         path = base.GetPathFromLink(permission_link)
         permission_id = base.GetResourceIdOrFullNameFromLink(permission_link)
-        return self.Read(path, "permissions", permission_id, None, options, **kwargs)
+        return self.Read(path, http_constants.ResourceType.Permission, permission_id, None, options, **kwargs)
 
     def ReadPermissions(
         self,
@@ -908,12 +910,12 @@ def QueryPermissions(
         if options is None:
             options = {}
 
-        path = base.GetPathFromLink(user_link, "permissions")
+        path = base.GetPathFromLink(user_link, http_constants.ResourceType.Permission)
         user_id = base.GetResourceIdOrFullNameFromLink(user_link)
 
         def fetch_fn(options: Mapping[str, Any]) -> Tuple[List[Dict[str, Any]], CaseInsensitiveDict]:
             return self.__QueryFeed(
-                    path, "permissions", user_id, lambda r: r["Permissions"],
+                    path, http_constants.ResourceType.Permission, user_id, lambda r: r["Permissions"],
                     lambda _, b: b, query, options, **kwargs)
 
         return ItemPaged(
@@ -947,7 +949,7 @@ def ReplaceUser(
         base._validate_resource(user)
         path = base.GetPathFromLink(user_link)
         user_id = base.GetResourceIdOrFullNameFromLink(user_link)
-        return self.Replace(user, path, "users", user_id, None, options, **kwargs)
+        return self.Replace(user, path, http_constants.ResourceType.User, user_id, None, options, **kwargs)
 
     def DeleteUser(
         self,
@@ -973,7 +975,7 @@ def DeleteUser(
 
         path = base.GetPathFromLink(user_link)
         user_id = base.GetResourceIdOrFullNameFromLink(user_link)
-        self.DeleteResource(path, "users", user_id, None, options, **kwargs)
+        self.DeleteResource(path, http_constants.ResourceType.User, user_id, None, options, **kwargs)
 
     def ReplacePermission(
         self,
@@ -1002,7 +1004,8 @@ def ReplacePermission(
         base._validate_resource(permission)
         path = base.GetPathFromLink(permission_link)
         permission_id = base.GetResourceIdOrFullNameFromLink(permission_link)
-        return self.Replace(permission, path, "permissions", permission_id, None, options, **kwargs)
+        return self.Replace(permission, path, http_constants.ResourceType.Permission, permission_id, None,
+                            options, **kwargs)
 
     def DeletePermission(
         self,
@@ -1028,7 +1031,8 @@ def DeletePermission(
 
         path = base.GetPathFromLink(permission_link)
         permission_id = base.GetResourceIdOrFullNameFromLink(permission_link)
-        self.DeleteResource(path, "permissions", permission_id, None, options, **kwargs)
+        self.DeleteResource(path, http_constants.ResourceType.Permission, permission_id, None, options,
+                            **kwargs)
 
     def ReadItems(
         self,
@@ -1091,13 +1095,13 @@ def QueryItems(
                 page_iterator_class=query_iterable.QueryIterable
             )
 
-        path = base.GetPathFromLink(database_or_container_link, "docs")
+        path = base.GetPathFromLink(database_or_container_link, http_constants.ResourceType.Document)
         collection_id = base.GetResourceIdOrFullNameFromLink(database_or_container_link)
 
         def fetch_fn(options: Mapping[str, Any]) -> Tuple[List[Dict[str, Any]], CaseInsensitiveDict]:
             return self.__QueryFeed(
                     path,
-                    "docs",
+                    http_constants.ResourceType.Document,
                     collection_id,
                     lambda r: r["Documents"],
                     lambda _, b: b,
@@ -1141,14 +1145,12 @@ def QueryItemsChangeFeed(
         if options is not None and "partitionKeyRangeId" in options:
             partition_key_range_id = options["partitionKeyRangeId"]
 
-        return self._QueryChangeFeed(
-            collection_link, "Documents", options, partition_key_range_id, response_hook=response_hook, **kwargs
-        )
+        return self._QueryChangeFeed(collection_link, options, partition_key_range_id,response_hook=response_hook,
+                                     **kwargs)
 
     def _QueryChangeFeed(
         self,
         collection_link: str,
-        resource_type: str,
         options: Optional[Mapping[str, Any]] = None,
         partition_key_range_id: Optional[str] = None,
         response_hook: Optional[Callable[[Mapping[str, Any], Mapping[str, Any]], None]] = None,
@@ -1157,7 +1159,6 @@ def _QueryChangeFeed(
         """Queries change feed of a resource in a collection.
 
         :param str collection_link: The link to the document collection.
-        :param str resource_type: The type of the resource.
         :param dict options: The request options for the request.
         :param str partition_key_range_id: Specifies partition key range id.
         :param response_hook: A callable invoked with the response metadata
@@ -1174,14 +1175,7 @@ def _QueryChangeFeed(
         else:
             options = dict(options)
 
-        resource_key_map = {"Documents": "docs"}
-
-        # For now, change feed only supports Documents and Partition Key Range resource type
-        if resource_type not in resource_key_map:
-            raise NotImplementedError(resource_type + " change feed query is not supported.")
-
-        resource_key = resource_key_map[resource_type]
-        path = base.GetPathFromLink(collection_link, resource_key)
+        path = base.GetPathFromLink(collection_link, http_constants.ResourceType.Document)
         collection_id = base.GetResourceIdOrFullNameFromLink(collection_link)
 
         def fetch_fn(options: Mapping[str, Any]) -> Tuple[List[Dict[str, Any]], CaseInsensitiveDict]:
@@ -1192,9 +1186,9 @@ def fetch_fn(options: Mapping[str, Any]) -> Tuple[List[Dict[str, Any]], CaseInse
                 options = new_options
             return self.__QueryFeed(
                 path,
-                resource_key,
+                http_constants.ResourceType.Document,
                 collection_id,
-                lambda r: r[resource_type],
+                lambda r: r["Documents"],
                 lambda _, b: b,
                 None,
                 options,
@@ -1253,12 +1247,13 @@ def _QueryPartitionKeyRanges(
         if options is None:
             options = {}
 
-        path = base.GetPathFromLink(collection_link, "pkranges")
+        path = base.GetPathFromLink(collection_link, http_constants.ResourceType.PartitionKeyRange)
         collection_id = base.GetResourceIdOrFullNameFromLink(collection_link)
 
         def fetch_fn(options: Mapping[str, Any]) -> Tuple[List[Dict[str, Any]], CaseInsensitiveDict]:
             return self.__QueryFeed(
-                    path, "pkranges", collection_id, lambda r: r["PartitionKeyRanges"],
+                    path, http_constants.ResourceType.PartitionKeyRange, collection_id,
+                    lambda r: r["PartitionKeyRanges"],
                     lambda _, b: b, query, options, **kwargs)
 
         return ItemPaged(
@@ -1299,7 +1294,8 @@ def CreateItem(
 
         if base.IsItemContainerLink(database_or_container_link):
             options = self._AddPartitionKey(database_or_container_link, document, options)
-        return self.Create(document, path, "docs", collection_id, None, options, **kwargs)
+        return self.Create(document, path, http_constants.ResourceType.Document, collection_id, None,
+                           options, **kwargs)
 
     def UpsertItem(
         self,
@@ -1335,7 +1331,8 @@ def UpsertItem(
         collection_id, document, path = self._GetContainerIdWithPathForItem(
             database_or_container_link, document, options
         )
-        return self.Upsert(document, path, "docs", collection_id, None, options, **kwargs)
+        return self.Upsert(document, path, http_constants.ResourceType.Document, collection_id, None,
+                           options, **kwargs)
 
     PartitionResolverErrorMessage = (
             "Couldn't find any partition resolvers for the database link provided. "
@@ -1373,7 +1370,7 @@ def _GetContainerIdWithPathForItem(
             else:
                 raise ValueError(CosmosClientConnection.PartitionResolverErrorMessage)
 
-        path = base.GetPathFromLink(collection_link, "docs")
+        path = base.GetPathFromLink(collection_link, http_constants.ResourceType.Document)
         collection_id = base.GetResourceIdOrFullNameFromLink(collection_link)
         return collection_id, document, path
 
@@ -1401,7 +1398,7 @@ def ReadItem(
 
         path = base.GetPathFromLink(document_link)
         document_id = base.GetResourceIdOrFullNameFromLink(document_link)
-        return self.Read(path, "docs", document_id, None, options, **kwargs)
+        return self.Read(path, http_constants.ResourceType.Document, document_id, None, options, **kwargs)
 
     def ReadTriggers(
         self,
@@ -1451,12 +1448,12 @@ def QueryTriggers(
         if options is None:
             options = {}
 
-        path = base.GetPathFromLink(collection_link, "triggers")
+        path = base.GetPathFromLink(collection_link, http_constants.ResourceType.Trigger)
         collection_id = base.GetResourceIdOrFullNameFromLink(collection_link)
 
         def fetch_fn(options: Mapping[str, Any]) -> Tuple[List[Dict[str, Any]], CaseInsensitiveDict]:
             return self.__QueryFeed(
-                path, "triggers", collection_id, lambda r: r["Triggers"],
+                path, http_constants.ResourceType.Trigger, collection_id, lambda r: r["Triggers"],
                 lambda _, b: b, query, options, **kwargs)
 
         return ItemPaged(
@@ -1488,7 +1485,7 @@ def CreateTrigger(
             options = {}
 
         collection_id, path, trigger = self._GetContainerIdWithPathForTrigger(collection_link, trigger)
-        return self.Create(trigger, path, "triggers", collection_id, None, options, **kwargs)
+        return self.Create(trigger, path, http_constants.ResourceType.Trigger, collection_id, None, options, **kwargs)
 
     def _GetContainerIdWithPathForTrigger(
         self,
@@ -1502,7 +1499,7 @@ def _GetContainerIdWithPathForTrigger(
         elif trigger.get("body"):
             trigger["body"] = str(trigger["body"])
 
-        path = base.GetPathFromLink(collection_link, "triggers")
+        path = base.GetPathFromLink(collection_link, http_constants.ResourceType.Trigger)
         collection_id = base.GetResourceIdOrFullNameFromLink(collection_link)
         return collection_id, path, trigger
 
@@ -1530,7 +1527,7 @@ def ReadTrigger(
 
         path = base.GetPathFromLink(trigger_link)
         trigger_id = base.GetResourceIdOrFullNameFromLink(trigger_link)
-        return self.Read(path, "triggers", trigger_id, None, options, **kwargs)
+        return self.Read(path, http_constants.ResourceType.Trigger, trigger_id, None, options, **kwargs)
 
     def UpsertTrigger(
         self,
@@ -1554,7 +1551,8 @@ def UpsertTrigger(
             options = {}
 
         collection_id, path, trigger = self._GetContainerIdWithPathForTrigger(collection_link, trigger)
-        return self.Upsert(trigger, path, "triggers", collection_id, None, options, **kwargs)
+        return self.Upsert(trigger, path, http_constants.ResourceType.Trigger, collection_id, None,
+                           options, **kwargs)
 
     def ReadUserDefinedFunctions(
         self,
@@ -1604,12 +1602,13 @@ def QueryUserDefinedFunctions(
         if options is None:
             options = {}
 
-        path = base.GetPathFromLink(collection_link, "udfs")
+        path = base.GetPathFromLink(collection_link, http_constants.ResourceType.UserDefinedFunction)
         collection_id = base.GetResourceIdOrFullNameFromLink(collection_link)
 
         def fetch_fn(options: Mapping[str, Any]) -> Tuple[List[Dict[str, Any]], CaseInsensitiveDict]:
             return self.__QueryFeed(
-                    path, "udfs", collection_id, lambda r: r["UserDefinedFunctions"],
+                    path, http_constants.ResourceType.UserDefinedFunction, collection_id,
+                    lambda r: r["UserDefinedFunctions"],
                     lambda _, b: b, query, options, **kwargs)
 
         return ItemPaged(
@@ -1641,7 +1640,8 @@ def CreateUserDefinedFunction(
             options = {}
 
         collection_id, path, udf = self._GetContainerIdWithPathForUDF(collection_link, udf)
-        return self.Create(udf, path, "udfs", collection_id, None, options, **kwargs)
+        return self.Create(udf, path, http_constants.ResourceType.UserDefinedFunction, collection_id, None,
+                           options, **kwargs)
 
     def UpsertUserDefinedFunction(
         self,
@@ -1668,7 +1668,8 @@ def UpsertUserDefinedFunction(
             options = {}
 
         collection_id, path, udf = self._GetContainerIdWithPathForUDF(collection_link, udf)
-        return self.Upsert(udf, path, "udfs", collection_id, None, options, **kwargs)
+        return self.Upsert(udf, path, http_constants.ResourceType.UserDefinedFunction, collection_id, None,
+                           options, **kwargs)
 
     def _GetContainerIdWithPathForUDF(
         self,
@@ -1682,7 +1683,7 @@ def _GetContainerIdWithPathForUDF(
         elif udf.get("body"):
             udf["body"] = str(udf["body"])
 
-        path = base.GetPathFromLink(collection_link, "udfs")
+        path = base.GetPathFromLink(collection_link, http_constants.ResourceType.UserDefinedFunction)
         collection_id = base.GetResourceIdOrFullNameFromLink(collection_link)
         return collection_id, path, udf
 
@@ -1710,7 +1711,7 @@ def ReadUserDefinedFunction(
 
         path = base.GetPathFromLink(udf_link)
         udf_id = base.GetResourceIdOrFullNameFromLink(udf_link)
-        return self.Read(path, "udfs", udf_id, None, options, **kwargs)
+        return self.Read(path, http_constants.ResourceType.UserDefinedFunction, udf_id, None, options, **kwargs)
 
     def ReadStoredProcedures(
         self,
@@ -1760,12 +1761,12 @@ def QueryStoredProcedures(
         if options is None:
             options = {}
 
-        path = base.GetPathFromLink(collection_link, "sprocs")
+        path = base.GetPathFromLink(collection_link, http_constants.ResourceType.StoredProcedure)
         collection_id = base.GetResourceIdOrFullNameFromLink(collection_link)
 
         def fetch_fn(options: Mapping[str, Any]) -> Tuple[List[Dict[str, Any]], CaseInsensitiveDict]:
             return self.__QueryFeed(
-                path, "sprocs", collection_id, lambda r: r["StoredProcedures"],
+                path, http_constants.ResourceType.StoredProcedure, collection_id, lambda r: r["StoredProcedures"],
                 lambda _, b: b, query, options, **kwargs)
 
         return ItemPaged(
@@ -1797,7 +1798,8 @@ def CreateStoredProcedure(
             options = {}
 
         collection_id, path, sproc = self._GetContainerIdWithPathForSproc(collection_link, sproc)
-        return self.Create(sproc, path, "sprocs", collection_id, None, options, **kwargs)
+        return self.Create(sproc, path, http_constants.ResourceType.StoredProcedure, collection_id, None,
+                           options, **kwargs)
 
     def UpsertStoredProcedure(
         self,
@@ -1824,7 +1826,8 @@ def UpsertStoredProcedure(
             options = {}
 
         collection_id, path, sproc = self._GetContainerIdWithPathForSproc(collection_link, sproc)
-        return self.Upsert(sproc, path, "sprocs", collection_id, None, options, **kwargs)
+        return self.Upsert(sproc, path, http_constants.ResourceType.StoredProcedure, collection_id, None,
+                           options, **kwargs)
 
     def _GetContainerIdWithPathForSproc(
         self,
@@ -1837,7 +1840,7 @@ def _GetContainerIdWithPathForSproc(
             sproc["body"] = str(sproc.pop("serverScript", ""))
         elif sproc.get("body"):
             sproc["body"] = str(sproc["body"])
-        path = base.GetPathFromLink(collection_link, "sprocs")
+        path = base.GetPathFromLink(collection_link, http_constants.ResourceType.StoredProcedure)
         collection_id = base.GetResourceIdOrFullNameFromLink(collection_link)
         return collection_id, path, sproc
 
@@ -1865,7 +1868,7 @@ def ReadStoredProcedure(
 
         path = base.GetPathFromLink(sproc_link)
         sproc_id = base.GetResourceIdOrFullNameFromLink(sproc_link)
-        return self.Read(path, "sprocs", sproc_id, None, options, **kwargs)
+        return self.Read(path, http_constants.ResourceType.StoredProcedure, sproc_id, None, options, **kwargs)
 
     def ReadConflicts(
         self,
@@ -1914,12 +1917,12 @@ def QueryConflicts(
         if options is None:
             options = {}
 
-        path = base.GetPathFromLink(collection_link, "conflicts")
+        path = base.GetPathFromLink(collection_link, http_constants.ResourceType.Conflict)
         collection_id = base.GetResourceIdOrFullNameFromLink(collection_link)
 
         def fetch_fn(options: Mapping[str, Any]) -> Tuple[List[Dict[str, Any]], CaseInsensitiveDict]:
             return self.__QueryFeed(
-                path, "conflicts", collection_id, lambda r: r["Conflicts"],
+                path, http_constants.ResourceType.Conflict, collection_id, lambda r: r["Conflicts"],
                 lambda _, b: b, query, options, **kwargs)
 
         return ItemPaged(
@@ -1949,7 +1952,7 @@ def ReadConflict(
 
         path = base.GetPathFromLink(conflict_link)
         conflict_id = base.GetResourceIdOrFullNameFromLink(conflict_link)
-        return self.Read(path, "conflicts", conflict_id, None, options, **kwargs)
+        return self.Read(path, http_constants.ResourceType.Conflict, conflict_id, None, options, **kwargs)
 
     def DeleteContainer(
         self,
@@ -1975,7 +1978,7 @@ def DeleteContainer(
 
         path = base.GetPathFromLink(collection_link)
         collection_id = base.GetResourceIdOrFullNameFromLink(collection_link)
-        self.DeleteResource(path, "colls", collection_id, None, options, **kwargs)
+        self.DeleteResource(path, http_constants.ResourceType.Collection, collection_id, None, options, **kwargs)
 
     def ReplaceItem(
         self,
@@ -2016,7 +2019,8 @@ def ReplaceItem(
         collection_link = base.GetItemContainerLink(document_link)
         options = self._AddPartitionKey(collection_link, new_document, options)
 
-        return self.Replace(new_document, path, "docs", document_id, None, options, **kwargs)
+        return self.Replace(new_document, path, http_constants.ResourceType.Document, document_id, None,
+                            options, **kwargs)
 
     def PatchItem(
         self,
@@ -2040,7 +2044,7 @@ def PatchItem(
         response_hook = kwargs.pop("response_hook", None)
         path = base.GetPathFromLink(document_link)
         document_id = base.GetResourceIdOrFullNameFromLink(document_link)
-        resource_type = "docs"
+        resource_type = http_constants.ResourceType.Document
         if options is None:
             options = {}
 
@@ -2085,7 +2089,7 @@ def Batch(
         if options is None:
             options = {}
 
-        path = base.GetPathFromLink(collection_link, "docs")
+        path = base.GetPathFromLink(collection_link, http_constants.ResourceType.Document)
         collection_id = base.GetResourceIdOrFullNameFromLink(collection_link)
         formatted_operations = base._format_batch_operations(batch_operations)
 
@@ -2134,9 +2138,10 @@ def _Batch(
     ) -> Tuple[List[Dict[str, Any]], CaseInsensitiveDict]:
         initial_headers = self.default_headers.copy()
         base._populate_batch_headers(initial_headers)
-        headers = base.GetHeaders(self, initial_headers, "post", path, collection_id, "docs",
+        headers = base.GetHeaders(self, initial_headers, "post", path, collection_id,
+                                  http_constants.ResourceType.Document,
                                   documents._OperationType.Batch, options)
-        request_params = RequestObject("docs", documents._OperationType.Batch)
+        request_params = RequestObject(http_constants.ResourceType.Document, documents._OperationType.Batch)
         request_params.set_excluded_location_from_options(options)
         return cast(
             Tuple[List[Dict[str, Any]], CaseInsensitiveDict],
@@ -2167,7 +2172,7 @@ def DeleteItem(
 
         path = base.GetPathFromLink(document_link)
         document_id = base.GetResourceIdOrFullNameFromLink(document_link)
-        self.DeleteResource(path, "docs", document_id, None, options, **kwargs)
+        self.DeleteResource(path, http_constants.ResourceType.Document, document_id, None, options, **kwargs)
 
     def DeleteAllItemsByPartitionKey(
         self,
@@ -2243,7 +2248,7 @@ def ReplaceTrigger(
 
         path = base.GetPathFromLink(trigger_link)
         trigger_id = base.GetResourceIdOrFullNameFromLink(trigger_link)
-        return self.Replace(trigger, path, "triggers", trigger_id, None, options, **kwargs)
+        return self.Replace(trigger, path, http_constants.ResourceType.Trigger, trigger_id, None, options, **kwargs)
 
     def DeleteTrigger(
         self,
@@ -2269,7 +2274,7 @@ def DeleteTrigger(
 
         path = base.GetPathFromLink(trigger_link)
         trigger_id = base.GetResourceIdOrFullNameFromLink(trigger_link)
-        self.DeleteResource(path, "triggers", trigger_id, None, options, **kwargs)
+        self.DeleteResource(path, http_constants.ResourceType.Trigger, trigger_id, None, options, **kwargs)
 
     def ReplaceUserDefinedFunction(
         self,
@@ -2304,7 +2309,7 @@ def ReplaceUserDefinedFunction(
 
         path = base.GetPathFromLink(udf_link)
         udf_id = base.GetResourceIdOrFullNameFromLink(udf_link)
-        return self.Replace(udf, path, "udfs", udf_id, None, options, **kwargs)
+        return self.Replace(udf, path, http_constants.ResourceType.UserDefinedFunction, udf_id, None, options, **kwargs)
 
     def DeleteUserDefinedFunction(
         self,
@@ -2330,7 +2335,7 @@ def DeleteUserDefinedFunction(
 
         path = base.GetPathFromLink(udf_link)
         udf_id = base.GetResourceIdOrFullNameFromLink(udf_link)
-        self.DeleteResource(path, "udfs", udf_id, None, options, **kwargs)
+        self.DeleteResource(path, http_constants.ResourceType.UserDefinedFunction, udf_id, None, options, **kwargs)
 
     def ExecuteStoredProcedure(
         self,
@@ -2365,11 +2370,13 @@ def ExecuteStoredProcedure(
 
         path = base.GetPathFromLink(sproc_link)
         sproc_id = base.GetResourceIdOrFullNameFromLink(sproc_link)
-        headers = base.GetHeaders(self, initial_headers, "post", path, sproc_id, "sprocs",
+        headers = base.GetHeaders(self, initial_headers, "post", path, sproc_id,
+                                  http_constants.ResourceType.StoredProcedure,
                                   documents._OperationType.ExecuteJavaScript, options)
 
         # ExecuteStoredProcedure will use WriteEndpoint since it uses POST operation
-        request_params = RequestObject("sprocs", documents._OperationType.ExecuteJavaScript)
+        request_params = RequestObject(http_constants.ResourceType.StoredProcedure,
+                                       documents._OperationType.ExecuteJavaScript)
         result, self.last_response_headers = self.__Post(path, request_params, params, headers, **kwargs)
         return result
 
@@ -2406,7 +2413,7 @@ def ReplaceStoredProcedure(
 
         path = base.GetPathFromLink(sproc_link)
         sproc_id = base.GetResourceIdOrFullNameFromLink(sproc_link)
-        return self.Replace(sproc, path, "sprocs", sproc_id, None, options, **kwargs)
+        return self.Replace(sproc, path, http_constants.ResourceType.StoredProcedure, sproc_id, None, options, **kwargs)
 
     def DeleteStoredProcedure(
         self,
@@ -2432,7 +2439,7 @@ def DeleteStoredProcedure(
 
         path = base.GetPathFromLink(sproc_link)
         sproc_id = base.GetResourceIdOrFullNameFromLink(sproc_link)
-        self.DeleteResource(path, "sprocs", sproc_id, None, options, **kwargs)
+        self.DeleteResource(path, http_constants.ResourceType.StoredProcedure, sproc_id, None, options, **kwargs)
 
     def DeleteConflict(
         self,
@@ -2458,7 +2465,7 @@ def DeleteConflict(
 
         path = base.GetPathFromLink(conflict_link)
         conflict_id = base.GetResourceIdOrFullNameFromLink(conflict_link)
-        self.DeleteResource(path, "conflicts", conflict_id, None, options, **kwargs)
+        self.DeleteResource(path, http_constants.ResourceType.Conflict, conflict_id, None, options, **kwargs)
 
     def ReplaceOffer(
         self,
@@ -2481,7 +2488,7 @@ def ReplaceOffer(
         base._validate_resource(offer)
         path = base.GetPathFromLink(offer_link)
         offer_id = base.GetResourceIdOrFullNameFromLink(offer_link)
-        return self.Replace(offer, path, "offers", offer_id, None, None, **kwargs)
+        return self.Replace(offer, path, http_constants.ResourceType.Offer, offer_id, None, None, **kwargs)
 
     def ReadOffer(
         self,
@@ -2498,7 +2505,7 @@ def ReadOffer(
         """
         path = base.GetPathFromLink(offer_link)
         offer_id = base.GetResourceIdOrFullNameFromLink(offer_link)
-        return self.Read(path, "offers", offer_id, None, {}, **kwargs)
+        return self.Read(path, http_constants.ResourceType.Offer, offer_id, None, {}, **kwargs)
 
     def ReadOffers(
         self,
@@ -2541,7 +2548,7 @@ def QueryOffers(
 
         def fetch_fn(options: Mapping[str, Any]) -> Tuple[List[Dict[str, Any]], CaseInsensitiveDict]:
             return self.__QueryFeed(
-                    "/offers", "offers", "", lambda r: r["Offers"],
+                    "/offers", http_constants.ResourceType.Offer, "", lambda r: r["Offers"],
                     lambda _, b: b, query, options, **kwargs)
 
         return ItemPaged(
@@ -2565,7 +2572,8 @@ def GetDatabaseAccount(
 
         headers = base.GetHeaders(self, self.default_headers, "get", "", "", "",
                                   documents._OperationType.Read,{}, client_id=self.client_id)
-        request_params = RequestObject("databaseaccount", documents._OperationType.Read, url_connection)
+        request_params = RequestObject(http_constants.ResourceType.DatabaseAccount, documents._OperationType.Read,
+                                       url_connection)
         result, last_response_headers = self.__Get("", request_params, headers, **kwargs)
         self.last_response_headers = last_response_headers
         database_account = DatabaseAccount()
@@ -2614,7 +2622,8 @@ def _GetDatabaseAccountCheck(
 
         headers = base.GetHeaders(self, self.default_headers, "get", "", "", "",
                                   documents._OperationType.Read,{}, client_id=self.client_id)
-        request_params = RequestObject("databaseaccount", documents._OperationType.Read, url_connection)
+        request_params = RequestObject(http_constants.ResourceType.DatabaseAccount, documents._OperationType.Read,
+                                       url_connection)
         self.__Get("", request_params, headers, **kwargs)
 
 
@@ -2998,7 +3007,7 @@ def QueryFeed(
         """
         return self.__QueryFeed(
                 path,
-                "docs",
+                http_constants.ResourceType.Document,
                 collection_id,
                 lambda r: r["Documents"],
                 lambda _, b: b,
@@ -3007,7 +3016,7 @@ def QueryFeed(
                 partition_key_range_id,
                 **kwargs)
 
-    def __QueryFeed(  # pylint: disable=too-many-locals, too-many-statements
+    def __QueryFeed(  # pylint: disable=too-many-locals, too-many-statements, too-many-branches
         self,
         path: str,
         resource_type: str,
@@ -3079,7 +3088,10 @@ def __GetBodiesFromQueryResult(result: Dict[str, Any]) -> List[Dict[str, Any]]:
 
             change_feed_state: Optional[ChangeFeedState] = options.get("changeFeedState")
             if change_feed_state is not None:
-                change_feed_state.populate_request_headers(self._routing_map_provider, headers)
+                feed_options = {}
+                if 'excludedLocations' in options:
+                    feed_options['excludedLocations'] = options['excludedLocations']
+                change_feed_state.populate_request_headers(self._routing_map_provider, headers, feed_options)
 
             result, last_response_headers = self.__Get(path, request_params, headers, **kwargs)
             self.last_response_headers = last_response_headers
@@ -3131,7 +3143,8 @@ def __GetBodiesFromQueryResult(result: Dict[str, Any]) -> List[Dict[str, Any]]:
             feedrangeEPK = partition_key_definition._get_epk_range_for_prefix_partition_key(
                 partition_key_value
             )  # cspell:disable-line
-            over_lapping_ranges = self._routing_map_provider.get_overlapping_ranges(resource_id, [feedrangeEPK])
+            over_lapping_ranges = self._routing_map_provider.get_overlapping_ranges(resource_id, [feedrangeEPK],
+                                                                                    options)
             # It is possible to get more than one over lapping range. We need to get the query results for each one
             results: Dict[str, Any] = {}
             # For each over lapping range we will take a sub range of the feed range EPK that overlaps with the over
@@ -3189,7 +3202,8 @@ def __GetBodiesFromQueryResult(result: Dict[str, Any]) -> List[Dict[str, Any]]:
 
         return __GetBodiesFromQueryResult(result), last_response_headers
 
-    def _GetQueryPlanThroughGateway(self, query: str, resource_link: str, **kwargs: Any) -> List[Dict[str, Any]]:
+    def _GetQueryPlanThroughGateway(self, query: str, resource_link: str, excluded_locations: Optional[str] = None,
+                                    **kwargs: Any) -> List[Dict[str, Any]]:
         supported_query_features = (documents._QueryFeature.Aggregate + "," +
                                     documents._QueryFeature.CompositeAggregate + "," +
                                     documents._QueryFeature.Distinct + "," +
@@ -3217,13 +3231,15 @@ def _GetQueryPlanThroughGateway(self, query: str, resource_link: str, **kwargs:
             "supportedQueryFeatures": supported_query_features,
             "queryVersion": http_constants.Versions.QueryVersion
         }
+        if excluded_locations is not None:
+            options["excludedLocations"] = excluded_locations
         resource_link = base.TrimBeginningAndEndingSlashes(resource_link)
-        path = base.GetPathFromLink(resource_link, "docs")
+        path = base.GetPathFromLink(resource_link, http_constants.ResourceType.Document)
         resource_id = base.GetResourceIdOrFullNameFromLink(resource_link)
 
         results, last_response_headers = self.__QueryFeed(
             path,
-            "docs",
+            http_constants.ResourceType.Document,
             resource_id,
             lambda r: cast(List[Dict[str, Any]], r),
             None,
diff --git a/sdk/cosmos/azure-cosmos/azure/cosmos/_execution_context/aio/execution_dispatcher.py b/sdk/cosmos/azure-cosmos/azure/cosmos/_execution_context/aio/execution_dispatcher.py
index a85c6f2c9955..dc4046bff2f4 100644
--- a/sdk/cosmos/azure-cosmos/azure/cosmos/_execution_context/aio/execution_dispatcher.py
+++ b/sdk/cosmos/azure-cosmos/azure/cosmos/_execution_context/aio/execution_dispatcher.py
@@ -72,8 +72,9 @@ async def __anext__(self):
         except CosmosHttpResponseError as e:
             if _is_partitioned_execution_info(e):
                 query_to_use = self._query if self._query is not None else "Select * from root r"
-                query_execution_info = _PartitionedQueryExecutionInfo(await self._client._GetQueryPlanThroughGateway
-                                                                      (query_to_use, self._resource_link))
+                query_plan_dict = await self._client._GetQueryPlanThroughGateway(
+                    query_to_use, self._resource_link, self._options.get('excludedLocations'))
+                query_execution_info = _PartitionedQueryExecutionInfo(query_plan_dict)
                 self._execution_context = await self._create_pipelined_execution_context(query_execution_info)
             else:
                 raise e
@@ -94,8 +95,9 @@ async def fetch_next_block(self):
         except CosmosHttpResponseError as e:
             if _is_partitioned_execution_info(e) or _is_hybrid_search_query(self._query, e):
                 query_to_use = self._query if self._query is not None else "Select * from root r"
-                query_execution_info = _PartitionedQueryExecutionInfo(await self._client._GetQueryPlanThroughGateway
-                                                                      (query_to_use, self._resource_link))
+                query_plan_dict = await self._client._GetQueryPlanThroughGateway(
+                    query_to_use, self._resource_link, self._options.get('excludedLocations'))
+                query_execution_info = _PartitionedQueryExecutionInfo(query_plan_dict)
                 self._execution_context = await self._create_pipelined_execution_context(query_execution_info)
             else:
                 raise e
diff --git a/sdk/cosmos/azure-cosmos/azure/cosmos/_execution_context/aio/multi_execution_aggregator.py b/sdk/cosmos/azure-cosmos/azure/cosmos/_execution_context/aio/multi_execution_aggregator.py
index 77b38baf61f2..9f6e2b51df00 100644
--- a/sdk/cosmos/azure-cosmos/azure/cosmos/_execution_context/aio/multi_execution_aggregator.py
+++ b/sdk/cosmos/azure-cosmos/azure/cosmos/_execution_context/aio/multi_execution_aggregator.py
@@ -162,7 +162,9 @@ async def _get_target_partition_key_range(self):
 
         query_ranges = self._partitioned_query_ex_info.get_query_ranges()
         return await self._routing_provider.get_overlapping_ranges(
-            self._resource_link, [routing_range.Range.ParseFromDict(range_as_dict) for range_as_dict in query_ranges]
+            self._resource_link,
+            [routing_range.Range.ParseFromDict(range_as_dict) for range_as_dict in query_ranges],
+            self._options
         )
 
     async def _configure_partition_ranges(self):
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 a93377e3dcf0..7cedfd23c7df 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
@@ -102,8 +102,9 @@ def __next__(self):
         except CosmosHttpResponseError as e:
             if _is_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))
+                query_plan_dict = self._client._GetQueryPlanThroughGateway(
+                    query_to_use, self._resource_link, self._options.get('excludedLocations'))
+                query_execution_info = _PartitionedQueryExecutionInfo(query_plan_dict)
                 self._execution_context = self._create_pipelined_execution_context(query_execution_info)
             else:
                 raise e
@@ -127,8 +128,9 @@ def fetch_next_block(self):
         except CosmosHttpResponseError as e:
             if _is_partitioned_execution_info(e) or _is_hybrid_search_query(self._query, 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))
+                query_plan_dict = self._client._GetQueryPlanThroughGateway(
+                    query_to_use, self._resource_link, self._options.get('excludedLocations'))
+                query_execution_info = _PartitionedQueryExecutionInfo(query_plan_dict)
                 self._execution_context = self._create_pipelined_execution_context(query_execution_info)
             else:
                 raise e
diff --git a/sdk/cosmos/azure-cosmos/azure/cosmos/_execution_context/multi_execution_aggregator.py b/sdk/cosmos/azure-cosmos/azure/cosmos/_execution_context/multi_execution_aggregator.py
index e1747c6d50ee..b7330b74c095 100644
--- a/sdk/cosmos/azure-cosmos/azure/cosmos/_execution_context/multi_execution_aggregator.py
+++ b/sdk/cosmos/azure-cosmos/azure/cosmos/_execution_context/multi_execution_aggregator.py
@@ -194,7 +194,9 @@ def _get_target_partition_key_range(self):
 
         query_ranges = self._partitioned_query_ex_info.get_query_ranges()
         return self._routing_provider.get_overlapping_ranges(
-            self._resource_link, [routing_range.Range.ParseFromDict(range_as_dict) for range_as_dict in query_ranges]
+            self._resource_link,
+            [routing_range.Range.ParseFromDict(range_as_dict) for range_as_dict in query_ranges],
+            self._options
         )
 
     next = __next__  # Python 2 compatibility.
diff --git a/sdk/cosmos/azure-cosmos/azure/cosmos/_location_cache.py b/sdk/cosmos/azure-cosmos/azure/cosmos/_location_cache.py
index f1bbfb77e670..ccb1528048be 100644
--- a/sdk/cosmos/azure-cosmos/azure/cosmos/_location_cache.py
+++ b/sdk/cosmos/azure-cosmos/azure/cosmos/_location_cache.py
@@ -28,8 +28,8 @@
 from typing import Set, Mapping, List
 from urllib.parse import urlparse
 
-from . import documents
-from . import http_constants
+from . import documents, _base as base
+from .http_constants import ResourceType
 from .documents import _OperationType
 from ._request_object import RequestObject
 
@@ -129,14 +129,23 @@ def _get_health_check_endpoints(regional_routing_contexts) -> Set[str]:
 def _get_applicable_regional_routing_contexts(regional_routing_contexts: List[RegionalRoutingContext],
                                               location_name_by_endpoint: Mapping[str, str],
                                               fall_back_regional_routing_context: RegionalRoutingContext,
-                                              exclude_location_list: List[str]) -> List[RegionalRoutingContext]:
+                                              exclude_location_list: List[str],
+                                              resource_type: str) -> List[RegionalRoutingContext]:
     # filter endpoints by excluded locations
     applicable_regional_routing_contexts = []
+    excluded_regional_routing_contexts = []
     for regional_routing_context in regional_routing_contexts:
         if location_name_by_endpoint.get(regional_routing_context.get_primary()) not in exclude_location_list:
             applicable_regional_routing_contexts.append(regional_routing_context)
+        else:
+            excluded_regional_routing_contexts.append(regional_routing_context)
+
+    # Preserves the excluded locations at the end of the list, because for the metadata API calls, excluded locations
+    # are not preferred, but all endpoints must be used.
+    if base.IsMasterResource(resource_type):
+        applicable_regional_routing_contexts.extend(excluded_regional_routing_contexts)
 
-    # if endpoint is empty add fallback endpoint
+    # If applicable_regional_routing_contexts is empty add fallback regional routing context
     if not applicable_regional_routing_contexts:
         applicable_regional_routing_contexts.append(fall_back_regional_routing_context)
 
@@ -159,8 +168,8 @@ def __init__(
         self.last_cache_update_time_stamp = 0
         self.account_read_regional_routing_contexts_by_location = {} # pylint: disable=name-too-long
         self.account_write_regional_routing_contexts_by_location = {} # pylint: disable=name-too-long
-        self.account_locations_by_read_regional_routing_context = {} # pylint: disable=name-too-long
-        self.account_locations_by_write_regional_routing_context = {} # pylint: disable=name-too-long
+        self.account_locations_by_read_endpoints = {} # pylint: disable=name-too-long
+        self.account_locations_by_write_endpoints = {} # pylint: disable=name-too-long
         self.account_write_locations = []
         self.account_read_locations = []
         self.connection_policy = connection_policy
@@ -212,9 +221,10 @@ def _get_applicable_read_regional_routing_contexts(self, request: RequestObject)
         if excluded_locations:
             return _get_applicable_regional_routing_contexts(
                 self.get_read_regional_routing_contexts(),
-                self.account_locations_by_read_regional_routing_context,
+                self.account_locations_by_read_endpoints,
                 self.get_write_regional_routing_contexts()[0],
-                excluded_locations)
+                excluded_locations,
+                request.resource_type)
 
         # Else, return all regional endpoints
         return self.get_read_regional_routing_contexts()
@@ -227,9 +237,10 @@ def _get_applicable_write_regional_routing_contexts(self, request: RequestObject
         if excluded_locations:
             return _get_applicable_regional_routing_contexts(
                 self.get_write_regional_routing_contexts(),
-                self.account_locations_by_write_regional_routing_context,
+                self.account_locations_by_write_endpoints,
                 self.default_regional_routing_context,
-                excluded_locations)
+                excluded_locations,
+                request.resource_type)
 
         # Else, return all regional endpoints
         return self.get_write_regional_routing_contexts()
@@ -358,7 +369,8 @@ def is_endpoint_unavailable_internal(self, endpoint: str, expected_available_ope
         # Endpoint is unavailable
         return True
 
-    def mark_endpoint_unavailable(self, unavailable_endpoint: str, unavailable_operation_type, refresh_cache: bool):
+    def mark_endpoint_unavailable(
+            self, unavailable_endpoint: str, unavailable_operation_type: str, refresh_cache: bool):
         logger.warning("Marking %s unavailable for %s ",
                        unavailable_endpoint,
                        unavailable_operation_type)
@@ -390,7 +402,7 @@ def update_location_cache(self, write_locations=None, read_locations=None, enabl
         if self.connection_policy.EnableEndpointDiscovery:
             if read_locations:
                 (self.account_read_regional_routing_contexts_by_location,
-                 self.account_locations_by_read_regional_routing_context,
+                 self.account_locations_by_read_endpoints,
                  self.account_read_locations) = get_endpoints_by_location(
                     read_locations,
                     self.account_read_regional_routing_contexts_by_location,
@@ -401,7 +413,7 @@ def update_location_cache(self, write_locations=None, read_locations=None, enabl
 
             if write_locations:
                 (self.account_write_regional_routing_contexts_by_location,
-                 self.account_locations_by_write_regional_routing_context,
+                 self.account_locations_by_write_endpoints,
                  self.account_write_locations) = get_endpoints_by_location(
                     write_locations,
                     self.account_write_regional_routing_contexts_by_location,
@@ -471,10 +483,10 @@ def can_use_multiple_write_locations(self):
 
     def can_use_multiple_write_locations_for_request(self, request):  # pylint: disable=name-too-long
         return self.can_use_multiple_write_locations() and (
-            request.resource_type == http_constants.ResourceType.Document
-            or request.resource_type == http_constants.ResourceType.PartitionKey
+            request.resource_type == ResourceType.Document
+            or request.resource_type == ResourceType.PartitionKey
             or (
-                request.resource_type == http_constants.ResourceType.StoredProcedure
+                request.resource_type == ResourceType.StoredProcedure
                 and request.operation_type == documents._OperationType.ExecuteJavaScript
             )
         )
diff --git a/sdk/cosmos/azure-cosmos/azure/cosmos/_request_object.py b/sdk/cosmos/azure-cosmos/azure/cosmos/_request_object.py
index e5b39c221016..2277aa4d0157 100644
--- a/sdk/cosmos/azure-cosmos/azure/cosmos/_request_object.py
+++ b/sdk/cosmos/azure-cosmos/azure/cosmos/_request_object.py
@@ -22,7 +22,6 @@
 """Represents a request object.
 """
 from typing import Optional, Mapping, Any
-from . import http_constants
 
 class RequestObject(object):
     def __init__(self, resource_type: str, operation_type: str, endpoint_override: Optional[str] = None) -> None:
@@ -56,15 +55,6 @@ def clear_route_to_location(self) -> None:
         self.location_endpoint_to_route = None
 
     def _can_set_excluded_location(self, options: Mapping[str, Any]) -> bool:
-        # If resource types for requests are not one of the followings, excluded locations cannot be set
-        acceptable_resource_types = [
-            http_constants.ResourceType.Document,
-            http_constants.ResourceType.PartitionKey,
-            http_constants.ResourceType.Collection,
-        ]
-        if self.resource_type.lower() not in acceptable_resource_types:
-            return False
-
         # If 'excludedLocations' wasn't in the options, excluded locations cannot be set
         if (options is None
             or 'excludedLocations' not in options):
diff --git a/sdk/cosmos/azure-cosmos/azure/cosmos/_routing/aio/routing_map_provider.py b/sdk/cosmos/azure-cosmos/azure/cosmos/_routing/aio/routing_map_provider.py
index e70ae355c495..5af0f082af2f 100644
--- a/sdk/cosmos/azure-cosmos/azure/cosmos/_routing/aio/routing_map_provider.py
+++ b/sdk/cosmos/azure-cosmos/azure/cosmos/_routing/aio/routing_map_provider.py
@@ -49,12 +49,13 @@ def __init__(self, client):
         # keeps the cached collection routing map by collection id
         self._collection_routing_map_by_item = {}
 
-    async def get_overlapping_ranges(self, collection_link, partition_key_ranges, **kwargs):
+    async def get_overlapping_ranges(self, collection_link, partition_key_ranges, feed_options = None, **kwargs):
         """Given a partition key range and a collection, return the list of
         overlapping partition key ranges.
 
         :param str collection_link: The name of the collection.
         :param list partition_key_ranges: List of partition key range.
+        :param dict feed_options: The request options.
         :return: List of overlapping partition key ranges.
         :rtype: list
         """
@@ -64,7 +65,8 @@ async def get_overlapping_ranges(self, collection_link, partition_key_ranges, **
 
         collection_routing_map = self._collection_routing_map_by_item.get(collection_id)
         if collection_routing_map is None:
-            collection_pk_ranges = [pk async for pk in cl._ReadPartitionKeyRanges(collection_link, **kwargs)]
+            collection_pk_ranges = [pk async for pk in
+                                    cl._ReadPartitionKeyRanges(collection_link, feed_options, **kwargs)]
             # for large collections, a split may complete between the read partition key ranges query page responses,
             # causing the partitionKeyRanges to have both the children ranges and their parents. Therefore, we need
             # to discard the parent ranges to have a valid routing map.
@@ -131,7 +133,7 @@ class SmartRoutingMapProvider(PartitionKeyRangeCache):
     invocation of CollectionRoutingMap.get_overlapping_ranges()
     """
 
-    async def get_overlapping_ranges(self, collection_link, partition_key_ranges, **kwargs):
+    async def get_overlapping_ranges(self, collection_link, partition_key_ranges, feed_options = None, **kwargs):
         """
         Given the sorted ranges and a collection,
         Returns the list of overlapping partition key ranges
@@ -139,6 +141,7 @@ async def get_overlapping_ranges(self, collection_link, partition_key_ranges, **
         :param str collection_link: The collection link.
         :param (list of routing_range.Range) partition_key_ranges:
             The sorted list of non-overlapping ranges.
+        :param dict feed_options: The request options.
         :return: List of partition key ranges.
         :rtype: list of dict
         :raises ValueError:
@@ -170,6 +173,7 @@ async def get_overlapping_ranges(self, collection_link, partition_key_ranges, **
                         self,
                         collection_link,
                         [queryRange],
+                        feed_options,
                         **kwargs)
                 assert overlappingRanges, "code bug: returned overlapping ranges for queryRange {} is empty".format(
                     queryRange
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 8dacb5190e07..901cf9f20899 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
@@ -50,12 +50,13 @@ def __init__(self, client):
         # keeps the cached collection routing map by collection id
         self._collection_routing_map_by_item = {}
 
-    def get_overlapping_ranges(self, collection_link, partition_key_ranges, **kwargs):
+    def get_overlapping_ranges(self, collection_link, partition_key_ranges, feed_options = None, **kwargs):
         """Given a partition key range and a collection, return the list of
         overlapping partition key ranges.
 
         :param str collection_link: The name of the collection.
         :param list partition_key_ranges: List of partition key range.
+        :param dict feed_options: The request options.
         :return: List of overlapping partition key ranges.
         :rtype: list
         """
@@ -65,7 +66,7 @@ def get_overlapping_ranges(self, collection_link, partition_key_ranges, **kwargs
 
         collection_routing_map = self._collection_routing_map_by_item.get(collection_id)
         if collection_routing_map is None:
-            collection_pk_ranges = list(cl._ReadPartitionKeyRanges(collection_link, **kwargs))
+            collection_pk_ranges = list(cl._ReadPartitionKeyRanges(collection_link, feed_options, **kwargs))
             # for large collections, a split may complete between the read partition key ranges query page responses,
             # causing the partitionKeyRanges to have both the children ranges and their parents. Therefore, we need
             # to discard the parent ranges to have a valid routing map.
@@ -132,7 +133,7 @@ class SmartRoutingMapProvider(PartitionKeyRangeCache):
     invocation of CollectionRoutingMap.get_overlapping_ranges()
     """
 
-    def get_overlapping_ranges(self, collection_link, partition_key_ranges, **kwargs):
+    def get_overlapping_ranges(self, collection_link, partition_key_ranges, feed_options = None, **kwargs):
         """
         Given the sorted ranges and a collection,
         Returns the list of overlapping partition key ranges
@@ -140,6 +141,7 @@ def get_overlapping_ranges(self, collection_link, partition_key_ranges, **kwargs
         :param str collection_link: The collection link.
         :param (list of routing_range.Range) partition_key_ranges:
             The sorted list of non-overlapping ranges.
+        :param dict feed_options: The request options.
         :return: List of partition key ranges.
         :rtype: list of dict
         :raises ValueError:
@@ -167,7 +169,12 @@ def get_overlapping_ranges(self, collection_link, partition_key_ranges, **kwargs
                     queryRange = currentProvidedRange
 
                 overlappingRanges = (
-                    PartitionKeyRangeCache.get_overlapping_ranges(self, collection_link, [queryRange], **kwargs))
+                    PartitionKeyRangeCache.get_overlapping_ranges(
+                        self,
+                        collection_link,
+                        [queryRange],
+                        feed_options,
+                        **kwargs))
                 assert overlappingRanges, "code bug: returned overlapping ranges for queryRange {} is empty".format(
                     queryRange
                 )
diff --git a/sdk/cosmos/azure-cosmos/azure/cosmos/aio/_container.py b/sdk/cosmos/azure-cosmos/azure/cosmos/aio/_container.py
index 4be3342dd26d..33cd804ab278 100644
--- a/sdk/cosmos/azure-cosmos/azure/cosmos/aio/_container.py
+++ b/sdk/cosmos/azure-cosmos/azure/cosmos/aio/_container.py
@@ -99,9 +99,15 @@ def __init__(
     def __repr__(self) -> str:
         return "<ContainerProxy [{}]>".format(self.container_link)[:1024]
 
-    async def _get_properties(self) -> Dict[str, Any]:
+    async def _get_properties_with_feed_options(self, feed_options: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
+        kwargs = {}
+        if feed_options and "excludedLocations" in feed_options:
+            kwargs['excluded_locations'] = feed_options['excludedLocations']
+        return await self._get_properties(**kwargs)
+
+    async def _get_properties(self, **kwargs: Any) -> Dict[str, Any]:
         if self.container_link not in self.client_connection._container_properties_cache:
-            await self.read()
+            await self.read(**kwargs)
         return self.client_connection._container_properties_cache[self.container_link]
 
     @property
@@ -140,9 +146,11 @@ async def _set_partition_key(
             return _return_undefined_or_empty_partition_key(await self.is_system_key)
         return cast(Union[str, int, float, bool, List[Union[str, int, float, bool]]], partition_key)
 
-    async def _get_epk_range_for_partition_key(self, partition_key_value: PartitionKeyType) -> Range:
-
-        container_properties = await self._get_properties()
+    async def _get_epk_range_for_partition_key(
+            self,
+            partition_key_value: PartitionKeyType,
+            feed_options: Optional[Dict[str, Any]] = None) -> Range:
+        container_properties = await self._get_properties_with_feed_options(feed_options)
         partition_key_definition = container_properties["partitionKey"]
         partition_key = PartitionKey(
             path=partition_key_definition["paths"],
@@ -172,6 +180,10 @@ async def read(
         :keyword Literal["High", "Low"] priority: Priority based execution allows users to set a priority for each
             request. Once the user has reached their provisioned throughput, low priority requests are throttled
             before high priority requests start getting throttled. Feature must first be enabled at the account level.
+        :keyword list[str] excluded_locations: Excluded locations to be skipped from preferred locations. The locations
+            in this list are specified as the names of the azure Cosmos locations like, 'West US', 'East US' and so on.
+            If all preferred locations were excluded, primary/hub location will be used.
+            This excluded_location will override existing excluded_locations in client level.
         :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.
@@ -514,7 +526,7 @@ def query_items(
             feed_options["enableScanInQuery"] = enable_scan_in_query
         if partition_key is not None:
             feed_options["partitionKey"] = self._set_partition_key(partition_key)
-            kwargs["containerProperties"] = self._get_properties
+            kwargs["containerProperties"] = self._get_properties_with_feed_options
         else:
             feed_options["enableCrossPartitionQuery"] = True
         if max_integrated_cache_staleness_in_ms:
@@ -749,16 +761,18 @@ def query_items_change_feed( # pylint: disable=unused-argument
         elif "start_time" in kwargs:
             change_feed_state_context["startTime"] = kwargs.pop("start_time")
         if "partition_key" in kwargs:
-            partition_key = kwargs.pop("partition_key")
-            change_feed_state_context["partitionKey"] = self._set_partition_key(cast(PartitionKeyType, partition_key))
-            change_feed_state_context["partitionKeyFeedRange"] = self._get_epk_range_for_partition_key(partition_key)
+            partition_key_value = kwargs.pop("partition_key")
+            change_feed_state_context["partitionKey"] = self._set_partition_key(
+                cast(PartitionKeyType, partition_key_value))
+            change_feed_state_context["partitionKeyFeedRange"] = self._get_epk_range_for_partition_key(
+                partition_key_value, feed_options)
         if "feed_range" in kwargs:
             change_feed_state_context["feedRange"] = kwargs.pop('feed_range')
         if "continuation" in feed_options:
             change_feed_state_context["continuation"] = feed_options.pop("continuation")
 
         feed_options["changeFeedStateContext"] = change_feed_state_context
-        feed_options["containerProperties"] = self._get_properties()
+        feed_options["containerProperties"] = self._get_properties_with_feed_options(feed_options)
 
         response_hook = kwargs.pop("response_hook", None)
         if hasattr(response_hook, "clear"):
diff --git a/sdk/cosmos/azure-cosmos/azure/cosmos/aio/_cosmos_client_connection_async.py b/sdk/cosmos/azure-cosmos/azure/cosmos/aio/_cosmos_client_connection_async.py
index 599c1cb5c0c6..5274e2adb502 100644
--- a/sdk/cosmos/azure-cosmos/azure/cosmos/aio/_cosmos_client_connection_async.py
+++ b/sdk/cosmos/azure-cosmos/azure/cosmos/aio/_cosmos_client_connection_async.py
@@ -345,7 +345,7 @@ def _GetDatabaseIdWithPathForUser(
         user: Mapping[str, Any]
     ) -> Tuple[Optional[str], str]:
         base._validate_resource(user)
-        path = base.GetPathFromLink(database_link, "users")
+        path = base.GetPathFromLink(database_link, http_constants.ResourceType.User)
         database_id = base.GetResourceIdOrFullNameFromLink(database_link)
         return database_id, path
 
@@ -360,7 +360,7 @@ def _GetContainerIdWithPathForSproc(
             sproc["body"] = str(sproc.pop("serverScript", ""))
         elif sproc.get("body"):
             sproc["body"] = str(sproc["body"])
-        path = base.GetPathFromLink(collection_link, "sprocs")
+        path = base.GetPathFromLink(collection_link, http_constants.ResourceType.StoredProcedure)
         collection_id = base.GetResourceIdOrFullNameFromLink(collection_link)
         return collection_id, path, sproc
 
@@ -376,7 +376,7 @@ def _GetContainerIdWithPathForTrigger(
         elif trigger.get("body"):
             trigger["body"] = str(trigger["body"])
 
-        path = base.GetPathFromLink(collection_link, "triggers")
+        path = base.GetPathFromLink(collection_link, http_constants.ResourceType.Trigger)
         collection_id = base.GetResourceIdOrFullNameFromLink(collection_link)
         return collection_id, path, trigger
 
@@ -392,7 +392,7 @@ def _GetContainerIdWithPathForUDF(
         elif udf.get("body"):
             udf["body"] = str(udf["body"])
 
-        path = base.GetPathFromLink(collection_link, "udfs")
+        path = base.GetPathFromLink(collection_link, http_constants.ResourceType.UserDefinedFunction)
         collection_id = base.GetResourceIdOrFullNameFromLink(collection_link)
         return collection_id, path, udf
 
@@ -418,7 +418,8 @@ async def GetDatabaseAccount(
                                   documents._OperationType.Read, {},
                                   client_id=self.client_id)  # path  # id  # type
 
-        request_params = _request_object.RequestObject("databaseaccount", documents._OperationType.Read, url_connection)
+        request_params = _request_object.RequestObject(http_constants.ResourceType.DatabaseAccount,
+                                                       documents._OperationType.Read, url_connection)
 
         result, self.last_response_headers = await self.__Get("", request_params, headers, **kwargs)
         database_account = documents.DatabaseAccount()
@@ -468,7 +469,8 @@ async def _GetDatabaseAccountCheck(
                                   documents._OperationType.Read, {},
                                   client_id=self.client_id)  # path  # id  # type
 
-        request_params = _request_object.RequestObject("databaseaccount", documents._OperationType.Read, url_connection)
+        request_params = _request_object.RequestObject(http_constants.ResourceType.DatabaseAccount,
+                                                       documents._OperationType.Read, url_connection)
         await self.__Get("", request_params, headers, **kwargs)
 
     async def CreateDatabase(
@@ -493,7 +495,7 @@ async def CreateDatabase(
 
         base._validate_resource(database)
         path = "/dbs"
-        return await self.Create(database, path, "dbs", None, None, options, **kwargs)
+        return await self.Create(database, path, http_constants.ResourceType.Database, None, None, options, **kwargs)
 
     async def CreateUser(
         self,
@@ -520,7 +522,7 @@ async def CreateUser(
             options = {}
 
         database_id, path = self._GetDatabaseIdWithPathForUser(database_link, user)
-        return await self.Create(user, path, "users", database_id, None, options, **kwargs)
+        return await self.Create(user, path, http_constants.ResourceType.User, database_id, None, options, **kwargs)
 
     async def CreateContainer(
         self,
@@ -545,9 +547,10 @@ async def CreateContainer(
             options = {}
 
         base._validate_resource(collection)
-        path = base.GetPathFromLink(database_link, "colls")
+        path = base.GetPathFromLink(database_link, http_constants.ResourceType.Collection)
         database_id = base.GetResourceIdOrFullNameFromLink(database_link)
-        return await self.Create(collection, path, "colls", database_id, None, options, **kwargs)
+        return await self.Create(collection, path, http_constants.ResourceType.Collection, database_id, None,
+                                 options, **kwargs)
 
     async def CreateItem(
         self,
@@ -589,7 +592,8 @@ async def CreateItem(
         if base.IsItemContainerLink(database_or_container_link):
             options = await self._AddPartitionKey(database_or_container_link, document, options)
 
-        return await self.Create(document, path, "docs", collection_id, None, options, **kwargs)
+        return await self.Create(document, path, http_constants.ResourceType.Document, collection_id, None,
+                                 options, **kwargs)
 
     async def CreatePermission(
         self,
@@ -642,7 +646,8 @@ async def CreateUserDefinedFunction(
             options = {}
 
         collection_id, path, udf = self._GetContainerIdWithPathForUDF(collection_link, udf)
-        return await self.Create(udf, path, "udfs", collection_id, None, options, **kwargs)
+        return await self.Create(udf, path, http_constants.ResourceType.UserDefinedFunction, collection_id, None,
+                                 options, **kwargs)
 
     async def CreateTrigger(
         self,
@@ -668,7 +673,8 @@ async def CreateTrigger(
             options = {}
 
         collection_id, path, trigger = self._GetContainerIdWithPathForTrigger(collection_link, trigger)
-        return await self.Create(trigger, path, "triggers", collection_id, None, options, **kwargs)
+        return await self.Create(trigger, path, http_constants.ResourceType.Trigger, collection_id, None,
+                                 options, **kwargs)
 
     async def CreateStoredProcedure(
         self,
@@ -694,7 +700,8 @@ async def CreateStoredProcedure(
             options = {}
 
         collection_id, path, sproc = self._GetContainerIdWithPathForSproc(collection_link, sproc)
-        return await self.Create(sproc, path, "sprocs", collection_id, None, options, **kwargs)
+        return await self.Create(sproc, path, http_constants.ResourceType.StoredProcedure, collection_id, None,
+                                 options, **kwargs)
 
     async def ExecuteStoredProcedure(
         self,
@@ -728,11 +735,13 @@ async def ExecuteStoredProcedure(
 
         path = base.GetPathFromLink(sproc_link)
         sproc_id = base.GetResourceIdOrFullNameFromLink(sproc_link)
-        headers = base.GetHeaders(self, initial_headers, "post", path, sproc_id, "sprocs",
+        headers = base.GetHeaders(self, initial_headers, "post", path, sproc_id,
+                                  http_constants.ResourceType.StoredProcedure,
                                   documents._OperationType.ExecuteJavaScript, options)
 
         # ExecuteStoredProcedure will use WriteEndpoint since it uses POST operation
-        request_params = _request_object.RequestObject("sprocs", documents._OperationType.ExecuteJavaScript)
+        request_params = _request_object.RequestObject(http_constants.ResourceType.StoredProcedure,
+                                                       documents._OperationType.ExecuteJavaScript)
         result, self.last_response_headers = await self.__Post(path, request_params, params, headers, **kwargs)
         return result
 
@@ -804,7 +813,7 @@ async def UpsertUser(
             options = {}
 
         database_id, path = self._GetDatabaseIdWithPathForUser(database_link, user)
-        return await self.Upsert(user, path, "users", database_id, None, options, **kwargs)
+        return await self.Upsert(user, path, http_constants.ResourceType.User, database_id, None, options, **kwargs)
 
     async def UpsertPermission(
         self,
@@ -872,7 +881,8 @@ async def UpsertItem(
         collection_id, document, path = self._GetContainerIdWithPathForItem(
             database_or_container_link, document, options
         )
-        return await self.Upsert(document, path, "docs", collection_id, None, options, **kwargs)
+        return await self.Upsert(document, path, http_constants.ResourceType.Document, collection_id, None,
+                                 options, **kwargs)
 
     async def Upsert(
         self,
@@ -972,7 +982,7 @@ async def ReadDatabase(
 
         path = base.GetPathFromLink(database_link)
         database_id = base.GetResourceIdOrFullNameFromLink(database_link)
-        return await self.Read(path, "dbs", database_id, None, options, **kwargs)
+        return await self.Read(path, http_constants.ResourceType.Database, database_id, None, options, **kwargs)
 
     async def ReadContainer(
         self,
@@ -998,7 +1008,7 @@ async def ReadContainer(
 
         path = base.GetPathFromLink(collection_link)
         collection_id = base.GetResourceIdOrFullNameFromLink(collection_link)
-        return await self.Read(path, "colls", collection_id, None, options, **kwargs)
+        return await self.Read(path, http_constants.ResourceType.Collection, collection_id, None, options, **kwargs)
 
     async def ReadItem(
         self,
@@ -1024,7 +1034,7 @@ async def ReadItem(
 
         path = base.GetPathFromLink(document_link)
         document_id = base.GetResourceIdOrFullNameFromLink(document_link)
-        return await self.Read(path, "docs", document_id, None, options, **kwargs)
+        return await self.Read(path, http_constants.ResourceType.Document, document_id, None, options, **kwargs)
 
     async def ReadUser(
         self,
@@ -1050,7 +1060,7 @@ async def ReadUser(
 
         path = base.GetPathFromLink(user_link)
         user_id = base.GetResourceIdOrFullNameFromLink(user_link)
-        return await self.Read(path, "users", user_id, None, options, **kwargs)
+        return await self.Read(path, http_constants.ResourceType.User, user_id, None, options, **kwargs)
 
     async def ReadPermission(
         self,
@@ -1102,7 +1112,7 @@ async def ReadUserDefinedFunction(
 
         path = base.GetPathFromLink(udf_link)
         udf_id = base.GetResourceIdOrFullNameFromLink(udf_link)
-        return await self.Read(path, "udfs", udf_id, None, options, **kwargs)
+        return await self.Read(path, http_constants.ResourceType.UserDefinedFunction, udf_id, None, options, **kwargs)
 
     async def ReadStoredProcedure(
         self,
@@ -1127,7 +1137,7 @@ async def ReadStoredProcedure(
 
         path = base.GetPathFromLink(sproc_link)
         sproc_id = base.GetResourceIdOrFullNameFromLink(sproc_link)
-        return await self.Read(path, "sprocs", sproc_id, None, options, **kwargs)
+        return await self.Read(path, http_constants.ResourceType.StoredProcedure, sproc_id, None, options, **kwargs)
 
     async def ReadTrigger(
         self,
@@ -1153,7 +1163,7 @@ async def ReadTrigger(
 
         path = base.GetPathFromLink(trigger_link)
         trigger_id = base.GetResourceIdOrFullNameFromLink(trigger_link)
-        return await self.Read(path, "triggers", trigger_id, None, options, **kwargs)
+        return await self.Read(path, http_constants.ResourceType.Trigger, trigger_id, None, options, **kwargs)
 
     async def ReadConflict(
         self,
@@ -1178,7 +1188,7 @@ async def ReadConflict(
 
         path = base.GetPathFromLink(conflict_link)
         conflict_id = base.GetResourceIdOrFullNameFromLink(conflict_link)
-        return await self.Read(path, "conflicts", conflict_id, None, options, **kwargs)
+        return await self.Read(path, http_constants.ResourceType.Conflict, conflict_id, None, options, **kwargs)
 
     async def Read(
         self,
@@ -1274,7 +1284,7 @@ async def ReplaceUser(
         base._validate_resource(user)
         path = base.GetPathFromLink(user_link)
         user_id = base.GetResourceIdOrFullNameFromLink(user_link)
-        return await self.Replace(user, path, "users", user_id, None, options, **kwargs)
+        return await self.Replace(user, path, http_constants.ResourceType.User, user_id, None, options, **kwargs)
 
     async def ReplacePermission(
         self,
@@ -1331,7 +1341,8 @@ async def ReplaceContainer(
         base._validate_resource(collection)
         path = base.GetPathFromLink(collection_link)
         collection_id = base.GetResourceIdOrFullNameFromLink(collection_link)
-        return await self.Replace(collection, path, "colls", collection_id, None, options, **kwargs)
+        return await self.Replace(collection, path, http_constants.ResourceType.Collection, collection_id, None,
+                                  options, **kwargs)
 
     async def ReplaceUserDefinedFunction(
         self,
@@ -1365,7 +1376,8 @@ async def ReplaceUserDefinedFunction(
 
         path = base.GetPathFromLink(udf_link)
         udf_id = base.GetResourceIdOrFullNameFromLink(udf_link)
-        return await self.Replace(udf, path, "udfs", udf_id, None, options, **kwargs)
+        return await self.Replace(udf, path, http_constants.ResourceType.UserDefinedFunction, udf_id, None,
+                                  options, **kwargs)
 
     async def ReplaceTrigger(
         self,
@@ -1399,7 +1411,8 @@ async def ReplaceTrigger(
 
         path = base.GetPathFromLink(trigger_link)
         trigger_id = base.GetResourceIdOrFullNameFromLink(trigger_link)
-        return await self.Replace(trigger, path, "triggers", trigger_id, None, options, **kwargs)
+        return await self.Replace(trigger, path, http_constants.ResourceType.Trigger, trigger_id, None,
+                                  options, **kwargs)
 
     async def ReplaceItem(
         self,
@@ -1439,7 +1452,8 @@ async def ReplaceItem(
         collection_link = base.GetItemContainerLink(document_link)
         options = await self._AddPartitionKey(collection_link, new_document, options)
 
-        return await self.Replace(new_document, path, "docs", document_id, None, options, **kwargs)
+        return await self.Replace(new_document, path, http_constants.ResourceType.Document, document_id, None,
+                                  options, **kwargs)
 
     async def PatchItem(
         self,
@@ -1462,7 +1476,7 @@ async def PatchItem(
         response_hook = kwargs.pop("response_hook", None)
         path = base.GetPathFromLink(document_link)
         document_id = base.GetResourceIdOrFullNameFromLink(document_link)
-        typ = "docs"
+        typ = http_constants.ResourceType.Document
 
         if options is None:
             options = {}
@@ -1507,7 +1521,7 @@ async def ReplaceOffer(
         base._validate_resource(offer)
         path = base.GetPathFromLink(offer_link)
         offer_id = base.GetResourceIdOrFullNameFromLink(offer_link)
-        return await self.Replace(offer, path, "offers", offer_id, None, None, **kwargs)
+        return await self.Replace(offer, path, http_constants.ResourceType.Offer, offer_id, None, None, **kwargs)
 
     async def ReplaceStoredProcedure(
         self,
@@ -1541,7 +1555,8 @@ async def ReplaceStoredProcedure(
 
         path = base.GetPathFromLink(sproc_link)
         sproc_id = base.GetResourceIdOrFullNameFromLink(sproc_link)
-        return await self.Replace(sproc, path, "sprocs", sproc_id, None, options, **kwargs)
+        return await self.Replace(sproc, path, http_constants.ResourceType.StoredProcedure, sproc_id, None,
+                                  options, **kwargs)
 
     async def Replace(
         self,
@@ -1668,7 +1683,7 @@ async def DeleteDatabase(
 
         path = base.GetPathFromLink(database_link)
         database_id = base.GetResourceIdOrFullNameFromLink(database_link)
-        await self.DeleteResource(path, "dbs", database_id, None, options, **kwargs)
+        await self.DeleteResource(path, http_constants.ResourceType.Database, database_id, None, options, **kwargs)
 
     async def DeleteUser(
         self,
@@ -1693,7 +1708,7 @@ async def DeleteUser(
 
         path = base.GetPathFromLink(user_link)
         user_id = base.GetResourceIdOrFullNameFromLink(user_link)
-        await self.DeleteResource(path, "users", user_id, None, options, **kwargs)
+        await self.DeleteResource(path, http_constants.ResourceType.User, user_id, None, options, **kwargs)
 
     async def DeletePermission(
         self,
@@ -1743,7 +1758,7 @@ async def DeleteContainer(
 
         path = base.GetPathFromLink(collection_link)
         collection_id = base.GetResourceIdOrFullNameFromLink(collection_link)
-        await self.DeleteResource(path, "colls", collection_id, None, options, **kwargs)
+        await self.DeleteResource(path, http_constants.ResourceType.Collection, collection_id, None, options, **kwargs)
 
     async def DeleteItem(
         self,
@@ -1768,7 +1783,7 @@ async def DeleteItem(
 
         path = base.GetPathFromLink(document_link)
         document_id = base.GetResourceIdOrFullNameFromLink(document_link)
-        await self.DeleteResource(path, "docs", document_id, None, options, **kwargs)
+        await self.DeleteResource(path, http_constants.ResourceType.Document, document_id, None, options, **kwargs)
 
     async def DeleteUserDefinedFunction(
         self,
@@ -1792,7 +1807,8 @@ async def DeleteUserDefinedFunction(
 
         path = base.GetPathFromLink(udf_link)
         udf_id = base.GetResourceIdOrFullNameFromLink(udf_link)
-        await self.DeleteResource(path, "udfs", udf_id, None, options, **kwargs)
+        await self.DeleteResource(path, http_constants.ResourceType.UserDefinedFunction, udf_id, None,
+                                  options, **kwargs)
 
     async def DeleteTrigger(
         self,
@@ -1817,7 +1833,7 @@ async def DeleteTrigger(
 
         path = base.GetPathFromLink(trigger_link)
         trigger_id = base.GetResourceIdOrFullNameFromLink(trigger_link)
-        await self.DeleteResource(path, "triggers", trigger_id, None, options, **kwargs)
+        await self.DeleteResource(path, http_constants.ResourceType.Trigger, trigger_id, None, options, **kwargs)
 
     async def DeleteStoredProcedure(
         self,
@@ -1842,7 +1858,7 @@ async def DeleteStoredProcedure(
 
         path = base.GetPathFromLink(sproc_link)
         sproc_id = base.GetResourceIdOrFullNameFromLink(sproc_link)
-        await self.DeleteResource(path, "sprocs", sproc_id, None, options, **kwargs)
+        await self.DeleteResource(path, http_constants.ResourceType.StoredProcedure, sproc_id, None, options, **kwargs)
 
     async def DeleteConflict(
         self,
@@ -1867,7 +1883,7 @@ async def DeleteConflict(
 
         path = base.GetPathFromLink(conflict_link)
         conflict_id = base.GetResourceIdOrFullNameFromLink(conflict_link)
-        await self.DeleteResource(path, "conflicts", conflict_id, None, options, **kwargs)
+        await self.DeleteResource(path, http_constants.ResourceType.Conflict, conflict_id, None, options, **kwargs)
 
     async def DeleteResource(
         self,
@@ -1960,7 +1976,7 @@ async def Batch(
         if options is None:
             options = {}
 
-        path = base.GetPathFromLink(collection_link, "docs")
+        path = base.GetPathFromLink(collection_link, http_constants.ResourceType.Document)
         collection_id = base.GetResourceIdOrFullNameFromLink(collection_link)
 
         formatted_operations = base._format_batch_operations(batch_operations)
@@ -2012,9 +2028,11 @@ async def _Batch(
     ) -> Tuple[List[Dict[str, Any]], CaseInsensitiveDict]:
         initial_headers = self.default_headers.copy()
         base._populate_batch_headers(initial_headers)
-        headers = base.GetHeaders(self, initial_headers, "post", path, collection_id, "docs",
+        headers = base.GetHeaders(self, initial_headers, "post", path, collection_id,
+                                  http_constants.ResourceType.Document,
                                   documents._OperationType.Batch, options)
-        request_params = _request_object.RequestObject("docs", documents._OperationType.Batch)
+        request_params = _request_object.RequestObject(http_constants.ResourceType.Document,
+                                                       documents._OperationType.Batch)
         request_params.set_excluded_location_from_options(options)
         result = await self.__Post(path, request_params, batch_operations, headers, **kwargs)
         return cast(Tuple[List[Dict[str, Any]], CaseInsensitiveDict], result)
@@ -2064,13 +2082,14 @@ def _QueryPartitionKeyRanges(
         if options is None:
             options = {}
 
-        path = base.GetPathFromLink(collection_link, "pkranges")
+        path = base.GetPathFromLink(collection_link, http_constants.ResourceType.PartitionKeyRange)
         collection_id = base.GetResourceIdOrFullNameFromLink(collection_link)
 
         async def fetch_fn(options: Mapping[str, Any]) -> Tuple[List[Dict[str, Any]], CaseInsensitiveDict]:
             return (
                 await self.__QueryFeed(
-                    path, "pkranges", collection_id, lambda r: r["PartitionKeyRanges"],
+                    path, http_constants.ResourceType.PartitionKeyRange, collection_id,
+                    lambda r: r["PartitionKeyRanges"],
                     lambda _, b: b, query, options, **kwargs
                 ),
                 self.last_response_headers,
@@ -2122,7 +2141,7 @@ def QueryDatabases(
         async def fetch_fn(options: Mapping[str, Any]) -> Tuple[List[Dict[str, Any]], CaseInsensitiveDict]:
             return (
                 await self.__QueryFeed(
-                    "/dbs", "dbs", "", lambda r: r["Databases"],
+                    "/dbs", http_constants.ResourceType.Database, "", lambda r: r["Databases"],
                     lambda _, b: b, query, options, **kwargs
                 ),
                 self.last_response_headers,
@@ -2176,13 +2195,13 @@ def QueryContainers(
         if options is None:
             options = {}
 
-        path = base.GetPathFromLink(database_link, "colls")
+        path = base.GetPathFromLink(database_link, http_constants.ResourceType.Collection)
         database_id = base.GetResourceIdOrFullNameFromLink(database_link)
 
         async def fetch_fn(options: Mapping[str, Any]) -> Tuple[List[Dict[str, Any]], CaseInsensitiveDict]:
             return (
                 await self.__QueryFeed(
-                    path, "colls", database_id, lambda r: r["DocumentCollections"],
+                    path, http_constants.ResourceType.Collection, database_id, lambda r: r["DocumentCollections"],
                     lambda _, body: body, query, options, **kwargs
                 ),
                 self.last_response_headers,
@@ -2253,14 +2272,14 @@ def QueryItems(
                 page_iterator_class=query_iterable.QueryIterable
             )
 
-        path = base.GetPathFromLink(database_or_container_link, "docs")
+        path = base.GetPathFromLink(database_or_container_link, http_constants.ResourceType.Document)
         collection_id = base.GetResourceIdOrFullNameFromLink(database_or_container_link)
 
         async def fetch_fn(options: Mapping[str, Any]) -> Tuple[List[Dict[str, Any]], CaseInsensitiveDict]:
             return (
                 await self.__QueryFeed(
                     path,
-                    "docs",
+                    http_constants.ResourceType.Document,
                     collection_id,
                     lambda r: r["Documents"],
                     lambda _, b: b,
@@ -2338,7 +2357,7 @@ def _QueryChangeFeed(
         else:
             options = dict(options)
 
-        resource_key_map = {"Documents": "docs"}
+        resource_key_map = {"Documents": http_constants.ResourceType.Document}
 
         # For now, change feed only supports Documents and Partition Key Range resource type
         if resource_type not in resource_key_map:
@@ -2400,7 +2419,8 @@ def QueryOffers(
         async def fetch_fn(options: Mapping[str, Any]) -> Tuple[List[Dict[str, Any]], CaseInsensitiveDict]:
             return (
                 await self.__QueryFeed(
-                    "/offers", "offers", "", lambda r: r["Offers"], lambda _, b: b, query, options, **kwargs
+                    "/offers", http_constants.ResourceType.Offer, "", lambda r: r["Offers"],
+                    lambda _, b: b, query, options, **kwargs
                 ),
                 self.last_response_headers,
             )
@@ -2459,13 +2479,13 @@ def QueryUsers(
         if options is None:
             options = {}
 
-        path = base.GetPathFromLink(database_link, "users")
+        path = base.GetPathFromLink(database_link, http_constants.ResourceType.User)
         database_id = base.GetResourceIdOrFullNameFromLink(database_link)
 
         async def fetch_fn(options: Mapping[str, Any]) -> Tuple[List[Dict[str, Any]], CaseInsensitiveDict]:
             return (
                 await self.__QueryFeed(
-                    path, "users", database_id, lambda r: r["Users"],
+                    path, http_constants.ResourceType.User, database_id, lambda r: r["Users"],
                     lambda _, b: b, query, options, **kwargs
                 ),
                 self.last_response_headers,
@@ -2582,13 +2602,13 @@ def QueryStoredProcedures(
         if options is None:
             options = {}
 
-        path = base.GetPathFromLink(collection_link, "sprocs")
+        path = base.GetPathFromLink(collection_link, http_constants.ResourceType.StoredProcedure)
         collection_id = base.GetResourceIdOrFullNameFromLink(collection_link)
 
         async def fetch_fn(options: Mapping[str, Any]) -> Tuple[List[Dict[str, Any]], CaseInsensitiveDict]:
             return (
                 await self.__QueryFeed(
-                    path, "sprocs", collection_id, lambda r: r["StoredProcedures"],
+                    path, http_constants.ResourceType.StoredProcedure, collection_id, lambda r: r["StoredProcedures"],
                     lambda _, b: b, query, options, **kwargs
                 ),
                 self.last_response_headers,
@@ -2644,13 +2664,14 @@ def QueryTriggers(
         if options is None:
             options = {}
 
-        path = base.GetPathFromLink(collection_link, "triggers")
+        path = base.GetPathFromLink(collection_link, http_constants.ResourceType.Trigger)
         collection_id = base.GetResourceIdOrFullNameFromLink(collection_link)
 
         async def fetch_fn(options: Mapping[str, Any]) -> Tuple[List[Dict[str, Any]], CaseInsensitiveDict]:
             return (
                 await self.__QueryFeed(
-                    path, "triggers", collection_id, lambda r: r["Triggers"], lambda _, b: b, query, options, **kwargs
+                    path, http_constants.ResourceType.Trigger, collection_id, lambda r: r["Triggers"],
+                    lambda _, b: b, query, options, **kwargs
                 ),
                 self.last_response_headers,
             )
@@ -2705,13 +2726,14 @@ def QueryUserDefinedFunctions(
         if options is None:
             options = {}
 
-        path = base.GetPathFromLink(collection_link, "udfs")
+        path = base.GetPathFromLink(collection_link, http_constants.ResourceType.UserDefinedFunction)
         collection_id = base.GetResourceIdOrFullNameFromLink(collection_link)
 
         async def fetch_fn(options: Mapping[str, Any]) -> Tuple[List[Dict[str, Any]], CaseInsensitiveDict]:
             return (
                 await self.__QueryFeed(
-                    path, "udfs", collection_id, lambda r: r["UserDefinedFunctions"],
+                    path, http_constants.ResourceType.UserDefinedFunction, collection_id,
+                    lambda r: r["UserDefinedFunctions"],
                     lambda _, b: b, query, options, **kwargs
                 ),
                 self.last_response_headers,
@@ -2766,13 +2788,13 @@ def QueryConflicts(
         if options is None:
             options = {}
 
-        path = base.GetPathFromLink(collection_link, "conflicts")
+        path = base.GetPathFromLink(collection_link, http_constants.ResourceType.Conflict)
         collection_id = base.GetResourceIdOrFullNameFromLink(collection_link)
 
         async def fetch_fn(options: Mapping[str, Any]) -> Tuple[List[Dict[str, Any]], CaseInsensitiveDict]:
             return (
                 await self.__QueryFeed(
-                    path, "conflicts", collection_id, lambda r: r["Conflicts"],
+                    path, http_constants.ResourceType.Conflict, collection_id, lambda r: r["Conflicts"],
                     lambda _, b: b, query, options, **kwargs
                 ),
                 self.last_response_headers,
@@ -2804,7 +2826,7 @@ async def QueryFeed(
         return (
             await self.__QueryFeed(
                 path,
-                "docs",
+                http_constants.ResourceType.Document,
                 collection_id,
                 lambda r: r["Documents"],
                 lambda _, b: b,
@@ -2878,7 +2900,11 @@ def __GetBodiesFromQueryResult(result: Dict[str, Any]) -> List[Dict[str, Any]]:
 
             change_feed_state: Optional[ChangeFeedState] = options.get("changeFeedState")
             if change_feed_state is not None:
-                await change_feed_state.populate_request_headers_async(self._routing_map_provider, headers)
+                feed_options = {}
+                if 'excludedLocations' in options:
+                    feed_options['excludedLocations'] = options['excludedLocations']
+                await change_feed_state.populate_request_headers_async(self._routing_map_provider, headers,
+                                                                       feed_options)
 
             result, self.last_response_headers = await self.__Get(path, request_params, headers, **kwargs)
             if response_hook:
@@ -2908,27 +2934,23 @@ def __GetBodiesFromQueryResult(result: Dict[str, Any]) -> List[Dict[str, Any]]:
 
         # check if query has prefix partition key
         cont_prop = kwargs.pop("containerProperties", None)
-        partition_key = options.get("partitionKey", None)
-        isPrefixPartitionQuery = False
-        partition_key_definition = None
+        partition_key_value = options.get("partitionKey", None)
+        is_prefix_partition_query = False
+        partition_key_obj = None
         if cont_prop:
-            cont_prop = await cont_prop()
-            pk_properties = cont_prop["partitionKey"]
-            partition_key_definition = PartitionKey(
-                path=pk_properties["paths"],
-                kind=pk_properties["kind"],
-                version=pk_properties["version"])
-            if partition_key_definition.kind == "MultiHash" and \
-                    (isinstance(partition_key, List) and \
-                     len(partition_key_definition['paths']) != len(partition_key)):
-                isPrefixPartitionQuery = True
-
-        if isPrefixPartitionQuery and partition_key_definition:
+            properties = await cont_prop(options)   # get properties with feed options
+            partition_key_definition = properties["partitionKey"]
+            partition_key_obj = PartitionKey(path=partition_key_definition["paths"],
+                                             kind=partition_key_definition["kind"],
+                                             version=partition_key_definition["version"])
+            is_prefix_partition_query = partition_key_obj._is_prefix_partition_key(partition_key_value)
+
+        if is_prefix_partition_query and partition_key_obj:
             # here get the overlapping ranges
             req_headers.pop(http_constants.HttpHeaders.PartitionKey, None)
-            feedrangeEPK = partition_key_definition._get_epk_range_for_prefix_partition_key(
-                partition_key)  # cspell:disable-line
-            over_lapping_ranges = await self._routing_map_provider.get_overlapping_ranges(id_, [feedrangeEPK])
+            feedrangeEPK = partition_key_obj._get_epk_range_for_prefix_partition_key(
+                partition_key_value)  # cspell:disable-line
+            over_lapping_ranges = await self._routing_map_provider.get_overlapping_ranges(id_, [feedrangeEPK], options)
             results: Dict[str, Any] = {}
             # For each over lapping range we will take a sub range of the feed range EPK that overlaps with the over
             # lapping physical partition. The EPK sub range will be one of four:
@@ -3079,7 +3101,7 @@ def _GetContainerIdWithPathForItem(self, database_or_container_link, document, o
             else:
                 raise ValueError(CosmosClientConnection.PartitionResolverErrorMessage)
 
-        path = base.GetPathFromLink(collection_link, "docs")
+        path = base.GetPathFromLink(collection_link, http_constants.ResourceType.Document)
         collection_id = base.GetResourceIdOrFullNameFromLink(collection_link)
         return collection_id, document, path
 
@@ -3129,8 +3151,8 @@ def GetPartitionResolver(self, database_link: str) -> Optional[RangePartitionRes
     # Adds the partition key to options
     async def _AddPartitionKey(self, collection_link, document, options):
         collection_link = base.TrimBeginningAndEndingSlashes(collection_link)
+        partitionKeyDefinition = await self._get_partition_key_definition(collection_link, options)
         new_options = dict(options)
-        partitionKeyDefinition = await self._get_partition_key_definition(collection_link)
         # If the collection doesn't have a partition key definition, skip it as it's a legacy collection
         if partitionKeyDefinition:
             # If the user has passed in the partitionKey in options use that else extract it from the document
@@ -3200,7 +3222,9 @@ async def _refresh_container_properties_cache(self, container_link: str):
         # Only cache Container Properties that will not change in the lifetime of the container
         self._set_container_properties_cache(container_link, _set_properties_cache(container))
 
-    async def _GetQueryPlanThroughGateway(self, query: str, resource_link: str, **kwargs) -> List[Dict[str, Any]]:
+    async def _GetQueryPlanThroughGateway(self, query: str, resource_link: str,
+                                          excluded_locations: Optional[str] = None,
+                                          **kwargs) -> List[Dict[str, Any]]:
         supported_query_features = (documents._QueryFeature.Aggregate + "," +
                                     documents._QueryFeature.CompositeAggregate + "," +
                                     documents._QueryFeature.Distinct + "," +
@@ -3228,14 +3252,15 @@ async def _GetQueryPlanThroughGateway(self, query: str, resource_link: str, **kw
             "supportedQueryFeatures": supported_query_features,
             "queryVersion": http_constants.Versions.QueryVersion
         }
-
+        if excluded_locations is not None:
+            options["excludedLocations"] = excluded_locations
         resource_link = base.TrimBeginningAndEndingSlashes(resource_link)
-        path = base.GetPathFromLink(resource_link, "docs")
+        path = base.GetPathFromLink(resource_link, http_constants.ResourceType.Document)
         resource_id = base.GetResourceIdOrFullNameFromLink(resource_link)
 
         return await self.__QueryFeed(
             path,
-            "docs",
+            http_constants.ResourceType.Document,
             resource_id,
             lambda r: cast(List[Dict[str, Any]], r),
             None,
@@ -3284,7 +3309,11 @@ async def DeleteAllItemsByPartitionKey(
         if response_hook:
             response_hook(last_response_headers, None)
 
-    async def _get_partition_key_definition(self, collection_link: str) -> Optional[Dict[str, Any]]:
+    async def _get_partition_key_definition(
+            self,
+            collection_link: str,
+            options: Mapping[str, Any]
+    ) -> Optional[Dict[str, Any]]:
         partition_key_definition: Optional[Dict[str, Any]]
         # If the document collection link is present in the cache, then use the cached partitionkey definition
         if collection_link in self.__container_properties_cache:
@@ -3292,7 +3321,7 @@ async def _get_partition_key_definition(self, collection_link: str) -> Optional[
             partition_key_definition = cached_container.get("partitionKey")
         # Else read the collection from backend and add it to the cache
         else:
-            container = await self.ReadContainer(collection_link)
+            container = await self.ReadContainer(collection_link, options)
             partition_key_definition = container.get("partitionKey")
             self.__container_properties_cache[collection_link] = _set_properties_cache(container)
         return partition_key_definition
diff --git a/sdk/cosmos/azure-cosmos/azure/cosmos/container.py b/sdk/cosmos/azure-cosmos/azure/cosmos/container.py
index 72459ede7a3a..ec9205967795 100644
--- a/sdk/cosmos/azure-cosmos/azure/cosmos/container.py
+++ b/sdk/cosmos/azure-cosmos/azure/cosmos/container.py
@@ -62,6 +62,22 @@
 
 PartitionKeyType = Union[str, int, float, bool, Sequence[Union[str, int, float, bool, None]], Type[NonePartitionKeyValue]]  # pylint: disable=line-too-long
 
+def get_partition_key_from_properties(container_properties: Dict[str, Any]) -> PartitionKey:
+    partition_key_definition = container_properties["partitionKey"]
+    return PartitionKey(
+        path=partition_key_definition["paths"],
+        kind=partition_key_definition["kind"],
+        version=partition_key_definition["version"])
+
+def is_prefix_partition_key(container_properties: Dict[str, Any], partition_key: PartitionKeyType) -> bool:
+    partition_key_obj: PartitionKey = get_partition_key_from_properties(container_properties)
+    return partition_key_obj._is_prefix_partition_key(partition_key)
+
+def get_epk_range_for_partition_key(
+        container_properties: Dict[str, Any],
+        partition_key_value: PartitionKeyType) -> Range:
+    partition_key_obj: PartitionKey = get_partition_key_from_properties(container_properties)
+    return partition_key_obj._get_epk_range_for_partition_key(partition_key_value)
 
 class ContainerProxy:  # pylint: disable=too-many-public-methods
     """An interface to interact with a specific DB Container.
@@ -97,9 +113,15 @@ def __init__(
     def __repr__(self) -> str:
         return "<ContainerProxy [{}]>".format(self.container_link)[:1024]
 
-    def _get_properties(self) -> Dict[str, Any]:
+    def _get_properties_with_feed_options(self, feed_options: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
+        kwargs = {}
+        if feed_options and "excludedLocations" in feed_options:
+            kwargs['excluded_locations'] = feed_options['excludedLocations']
+        return self._get_properties(**kwargs)
+
+    def _get_properties(self, **kwargs: Any) -> Dict[str, Any]:
         if self.container_link not in self.__get_client_container_caches():
-            self.read()
+            self.read(**kwargs)
         return self.__get_client_container_caches()[self.container_link]
 
     @property
@@ -135,16 +157,6 @@ def _set_partition_key(
             return _return_undefined_or_empty_partition_key(self.is_system_key)
         return cast(Union[str, int, float, bool, List[Union[str, int, float, bool]]], partition_key)
 
-    def _get_epk_range_for_partition_key( self, partition_key_value: PartitionKeyType) -> Range:
-        container_properties = self._get_properties()
-        partition_key_definition = container_properties["partitionKey"]
-        partition_key = PartitionKey(
-            path=partition_key_definition["paths"],
-            kind=partition_key_definition["kind"],
-            version=partition_key_definition["version"])
-
-        return partition_key._get_epk_range_for_partition_key(partition_key_value)
-
     def __get_client_container_caches(self) -> Dict[str, Dict[str, Any]]:
         return self.client_connection._container_properties_cache
 
@@ -561,16 +573,18 @@ def query_items_change_feed(
             change_feed_state_context["startTime"] = "Beginning"
         elif "start_time" in kwargs:
             change_feed_state_context["startTime"] = kwargs.pop("start_time")
+
+        container_properties = self._get_properties_with_feed_options(feed_options)
         if "partition_key" in kwargs:
             partition_key = kwargs.pop("partition_key")
             change_feed_state_context["partitionKey"] = self._set_partition_key(cast(PartitionKeyType, partition_key))
-            change_feed_state_context["partitionKeyFeedRange"] = self._get_epk_range_for_partition_key(partition_key)
+            change_feed_state_context["partitionKeyFeedRange"] = \
+                get_epk_range_for_partition_key(container_properties, partition_key)
         if "feed_range" in kwargs:
             change_feed_state_context["feedRange"] = kwargs.pop('feed_range')
         if "continuation" in feed_options:
             change_feed_state_context["continuation"] = feed_options.pop("continuation")
 
-        container_properties = self._get_properties()
         feed_options["changeFeedStateContext"] = change_feed_state_context
         feed_options["containerRID"] = container_properties["_rid"]
 
@@ -684,9 +698,9 @@ def query_items(  # pylint:disable=docstring-missing-param
             feed_options["populateIndexMetrics"] = populate_index_metrics
         if partition_key is not None:
             partition_key_value = self._set_partition_key(partition_key)
-            if self.__is_prefix_partitionkey(partition_key):
+            properties = self._get_properties_with_feed_options(feed_options)
+            if is_prefix_partition_key(properties, partition_key):
                 kwargs["isPrefixPartitionQuery"] = True
-                properties = self._get_properties()
                 kwargs["partitionKeyDefinition"] = properties["partitionKey"]
                 kwargs["partitionKeyDefinition"]["partition_key"] = partition_key_value
             else:
@@ -714,17 +728,6 @@ def query_items(  # pylint:disable=docstring-missing-param
         )
         return items
 
-    def __is_prefix_partitionkey(
-        self, partition_key: PartitionKeyType) -> bool:
-        properties = self._get_properties()
-        pk_properties = properties["partitionKey"]
-        partition_key_definition = PartitionKey(
-            path=pk_properties["paths"],
-            kind=pk_properties["kind"],
-            version=pk_properties["version"])
-        return partition_key_definition._is_prefix_partition_key(partition_key)
-
-
     @distributed_trace
     def replace_item(  # pylint:disable=docstring-missing-param
         self,
@@ -1586,7 +1589,9 @@ def feed_range_from_partition_key(self, partition_key: PartitionKeyType) -> Dict
           are present. It therefore should only be treated as an opaque value.
 
         """
-        return FeedRangeInternalEpk(self._get_epk_range_for_partition_key(partition_key)).to_dict()
+        container_properties = self._get_properties()
+        epk_range_for_partition_key = get_epk_range_for_partition_key(container_properties, partition_key)
+        return FeedRangeInternalEpk(epk_range_for_partition_key).to_dict()
 
     def is_feed_range_subset(self, parent_feed_range: Dict[str, Any], child_feed_range: Dict[str, Any]) -> bool:
         """ Checks if child feed range is a subset of parent feed range.
diff --git a/sdk/cosmos/azure-cosmos/azure/cosmos/http_constants.py b/sdk/cosmos/azure-cosmos/azure/cosmos/http_constants.py
index 08e104d69de7..81804f71dfef 100644
--- a/sdk/cosmos/azure-cosmos/azure/cosmos/http_constants.py
+++ b/sdk/cosmos/azure-cosmos/azure/cosmos/http_constants.py
@@ -467,16 +467,3 @@ class ResourceType:
     Topology = "topology"
     DatabaseAccount = "databaseaccount"
     PartitionKey = "partitionkey"
-
-    @staticmethod
-    def IsCollectionChild(resourceType: str) -> bool:
-        return resourceType in (
-            ResourceType.Document,
-            ResourceType.Attachment,
-            ResourceType.Conflict,
-            ResourceType.Schema,
-            ResourceType.UserDefinedFunction,
-            ResourceType.Trigger,
-            ResourceType.StoredProcedure,
-            ResourceType.PartitionKey,
-        )
diff --git a/sdk/cosmos/azure-cosmos/tests/_fault_injection_transport.py b/sdk/cosmos/azure-cosmos/tests/_fault_injection_transport.py
index 6a56daf4a789..1413a30e78cc 100644
--- a/sdk/cosmos/azure-cosmos/tests/_fault_injection_transport.py
+++ b/sdk/cosmos/azure-cosmos/tests/_fault_injection_transport.py
@@ -40,6 +40,8 @@
 
 from azure.cosmos.http_constants import ResourceType, HttpHeaders
 
+ERROR_WITH_COUNTER = "error_with_counter"
+
 class FaultInjectionTransport(RequestsTransport):
     logger = logging.getLogger('azure.cosmos.fault_injection_transport')
     logger.setLevel(logging.DEBUG)
@@ -48,8 +50,19 @@ def __init__(self, *, session: Optional[Session] = None, loop=None, session_owne
         self.faults: List[Dict[str, Any]] = []
         self.requestTransformations: List[Dict[str, Any]]  = []
         self.responseTransformations: List[Dict[str, Any]] = []
+        self.counters: Dict[str, int] = {
+            ERROR_WITH_COUNTER: 0
+        }
         super().__init__(session=session, loop=loop, session_owner=session_owner, **config)
 
+    def reset_counters(self):
+        for name in self.counters:
+            self.counters[name] = 0
+
+    def error_with_counter(self, error: Exception) -> Exception:
+        self.counters[ERROR_WITH_COUNTER] += 1
+        return error
+
     def add_fault(self, predicate: Callable[[HttpRequest], bool], fault_factory: Callable[[HttpRequest], Exception]):
         self.faults.append({"predicate": predicate, "apply": fault_factory})
 
@@ -142,6 +155,11 @@ def predicate_is_document_operation(r: HttpRequest) -> bool:
         is_document_operation = r.headers.get(HttpHeaders.ThinClientProxyResourceType) == ResourceType.Document
         return is_document_operation
 
+    @staticmethod
+    def predicate_is_resource_type(r: HttpRequest, resource_type: str) -> bool:
+        is_resource_type = r.headers.get(HttpHeaders.ThinClientProxyResourceType) == resource_type
+        return is_resource_type
+
     @staticmethod
     def predicate_is_operation_type(r: HttpRequest, operation_type: str) -> bool:
         is_operation_type = r.headers.get(HttpHeaders.ThinClientProxyOperationType) == operation_type
diff --git a/sdk/cosmos/azure-cosmos/tests/_fault_injection_transport_async.py b/sdk/cosmos/azure-cosmos/tests/_fault_injection_transport_async.py
index 95a46a7696e0..994357323b81 100644
--- a/sdk/cosmos/azure-cosmos/tests/_fault_injection_transport_async.py
+++ b/sdk/cosmos/azure-cosmos/tests/_fault_injection_transport_async.py
@@ -37,6 +37,7 @@
 from azure.core.exceptions import ServiceRequestError, ServiceResponseError
 
 from azure.cosmos.http_constants import ResourceType, HttpHeaders
+from _fault_injection_transport import ERROR_WITH_COUNTER
 
 class FaultInjectionTransportAsync(AioHttpTransport):
     logger = logging.getLogger('azure.cosmos.fault_injection_transport_async')
@@ -46,8 +47,19 @@ def __init__(self, *, session: Optional[aiohttp.ClientSession] = None, loop=None
         self.faults: List[Dict[str, Any]] = []
         self.requestTransformations: List[Dict[str, Any]]  = []
         self.responseTransformations: List[Dict[str, Any]] = []
+        self.counters: Dict[str, int] = {
+            ERROR_WITH_COUNTER: 0
+        }
         super().__init__(session=session, loop=loop, session_owner=session_owner, **config)
 
+    async def reset_counters(self):
+        for name in self.counters:
+            self.counters[name] = 0
+
+    async def error_with_counter(self, error: Exception) -> Exception:
+        self.counters[ERROR_WITH_COUNTER] += 1
+        return error
+
     def add_fault(self, predicate: Callable[[HttpRequest], bool], fault_factory: Callable[[HttpRequest], Awaitable[Exception]]):
         self.faults.append({"predicate": predicate, "apply": fault_factory})
 
@@ -142,6 +154,11 @@ def predicate_is_document_operation(r: HttpRequest) -> bool:
 
         return is_document_operation
 
+    @staticmethod
+    def predicate_is_resource_type(r: HttpRequest, resource_type: str) -> bool:
+        is_resource_type = r.headers.get(HttpHeaders.ThinClientProxyResourceType) == resource_type
+        return is_resource_type
+
     @staticmethod
     def predicate_is_operation_type(r: HttpRequest, operation_type: str) -> bool:
         is_operation_type = r.headers.get(HttpHeaders.ThinClientProxyOperationType) == operation_type
diff --git a/sdk/cosmos/azure-cosmos/tests/conftest.py b/sdk/cosmos/azure-cosmos/tests/conftest.py
index 0842d837931f..f163d5e48718 100644
--- a/sdk/cosmos/azure-cosmos/tests/conftest.py
+++ b/sdk/cosmos/azure-cosmos/tests/conftest.py
@@ -24,7 +24,8 @@ def pytest_sessionstart(session):
     config.create_database_if_not_exist(cosmos_sync_client)
     config.create_single_partition_container_if_not_exist(cosmos_sync_client)
     config.create_multi_partition_container_if_not_exist(cosmos_sync_client)
-
+    config.create_single_partition_prefix_pk_container_if_not_exist(cosmos_sync_client)
+    config.create_multi_partition_prefix_pk_container_if_not_exist(cosmos_sync_client)
 
 def pytest_sessionfinish(session, exitstatus):
     """
diff --git a/sdk/cosmos/azure-cosmos/tests/routing/test_routing_map_provider.py b/sdk/cosmos/azure-cosmos/tests/routing/test_routing_map_provider.py
index cc69d630c162..17c9b7c1dce2 100644
--- a/sdk/cosmos/azure-cosmos/tests/routing/test_routing_map_provider.py
+++ b/sdk/cosmos/azure-cosmos/tests/routing/test_routing_map_provider.py
@@ -9,6 +9,7 @@
 from azure.cosmos._routing.routing_map_provider import CollectionRoutingMap
 from azure.cosmos._routing.routing_map_provider import SmartRoutingMapProvider
 
+from typing import Optional, Mapping, Any
 
 @pytest.mark.cosmosEmulator
 class TestRoutingMapProvider(unittest.TestCase):
@@ -17,7 +18,7 @@ class MockedCosmosClientConnection(object):
         def __init__(self, partition_key_ranges):
             self.partition_key_ranges = partition_key_ranges
 
-        def _ReadPartitionKeyRanges(self, collection_link):
+        def _ReadPartitionKeyRanges(self, collection_link: str, feed_options: Optional[Mapping[str, Any]] = None):
             return self.partition_key_ranges
 
     def setUp(self):
diff --git a/sdk/cosmos/azure-cosmos/tests/test_config.py b/sdk/cosmos/azure-cosmos/tests/test_config.py
index cbab2f07f710..232fe9fcd5cd 100644
--- a/sdk/cosmos/azure-cosmos/tests/test_config.py
+++ b/sdk/cosmos/azure-cosmos/tests/test_config.py
@@ -61,8 +61,12 @@ class TestConfig(object):
 
     TEST_SINGLE_PARTITION_CONTAINER_ID = "Single Partition Test Container " + str(uuid.uuid4())
     TEST_MULTI_PARTITION_CONTAINER_ID = "Multi Partition Test Container " + str(uuid.uuid4())
+    TEST_SINGLE_PARTITION_PREFIX_PK_CONTAINER_ID = "Single Partition With Prefix PK Test Container " + str(uuid.uuid4())
+    TEST_MULTI_PARTITION_PREFIX_PK_CONTAINER_ID = "Multi Partition With Prefix PK Test Container " + str(uuid.uuid4())
 
     TEST_CONTAINER_PARTITION_KEY = "pk"
+    TEST_CONTAINER_PREFIX_PARTITION_KEY = ["pk1", "pk2"]
+    TEST_CONTAINER_PREFIX_PARTITION_KEY_PATH = ['/pk1', '/pk2']
 
     @classmethod
     def create_database_if_not_exist(cls, client):
@@ -91,6 +95,26 @@ def create_multi_partition_container_if_not_exist(cls, client):
             offer_throughput=cls.THROUGHPUT_FOR_5_PARTITIONS)
         return document_collection
 
+    @classmethod
+    def create_single_partition_prefix_pk_container_if_not_exist(cls, client):
+        # type: (CosmosClient) -> ContainerProxy
+        database = cls.create_database_if_not_exist(client)
+        document_collection = database.create_container_if_not_exists(
+            id=cls.TEST_SINGLE_PARTITION_PREFIX_PK_CONTAINER_ID,
+            partition_key=PartitionKey(path=cls.TEST_CONTAINER_PREFIX_PARTITION_KEY_PATH, kind='MultiHash'),
+            offer_throughput=cls.THROUGHPUT_FOR_1_PARTITION)
+        return document_collection
+
+    @classmethod
+    def create_multi_partition_prefix_pk_container_if_not_exist(cls, client):
+        # type: (CosmosClient) -> ContainerProxy
+        database = cls.create_database_if_not_exist(client)
+        document_collection = database.create_container_if_not_exists(
+            id=cls.TEST_MULTI_PARTITION_PREFIX_PK_CONTAINER_ID,
+            partition_key=PartitionKey(path=cls.TEST_CONTAINER_PREFIX_PARTITION_KEY_PATH, kind='MultiHash'),
+            offer_throughput=cls.THROUGHPUT_FOR_5_PARTITIONS)
+        return document_collection
+
     @classmethod
     def try_delete_database(cls, client):
         # type: (CosmosClient) -> None
diff --git a/sdk/cosmos/azure-cosmos/tests/test_excluded_locations.py b/sdk/cosmos/azure-cosmos/tests/test_excluded_locations.py
index 4b517796dd3a..041e8f63789e 100644
--- a/sdk/cosmos/azure-cosmos/tests/test_excluded_locations.py
+++ b/sdk/cosmos/azure-cosmos/tests/test_excluded_locations.py
@@ -6,11 +6,9 @@
 import uuid
 import test_config
 import pytest
+import time
 
-import azure.cosmos.cosmos_client as cosmos_client
-from azure.cosmos.partition_key import PartitionKey
-from azure.cosmos.exceptions import CosmosResourceNotFoundError
-
+from azure.cosmos import CosmosClient
 
 class MockHandler(logging.Handler):
     def __init__(self):
@@ -23,16 +21,27 @@ def reset(self):
     def emit(self, record):
         self.messages.append(record.msg)
 
+# Test configurations
+class TestDataType:
+    CLIENT_ONLY_TESTS = 'clientOnlyTests'
+    CLIENT_AND_REQUEST_TESTS = 'clientAndRequestTests'
+    ALL_TESTS = 'allTests'
+
+TEST_DATA_TYPE = TestDataType.ALL_TESTS
+
 MOCK_HANDLER = MockHandler()
 CONFIG = test_config.TestConfig()
 HOST = CONFIG.host
 KEY = CONFIG.masterKey
 DATABASE_ID = CONFIG.TEST_DATABASE_ID
-CONTAINER_ID = CONFIG.TEST_SINGLE_PARTITION_CONTAINER_ID
-PARTITION_KEY = CONFIG.TEST_CONTAINER_PARTITION_KEY
+CONTAINER_ID = CONFIG.TEST_MULTI_PARTITION_PREFIX_PK_CONTAINER_ID
+PARTITION_KEY = CONFIG.TEST_CONTAINER_PREFIX_PARTITION_KEY
 ITEM_ID = 'doc1'
-ITEM_PK_VALUE = 'pk'
-TEST_ITEM = {'id': ITEM_ID, PARTITION_KEY: ITEM_PK_VALUE}
+PARTITION_KEY_VALUES = [f'value{i+1}' for i in range(len(PARTITION_KEY))]
+PREFIX_PARTITION_KEY = [PARTITION_KEY_VALUES[0]]
+PARTITION_KEY_ITEMS = dict(zip(PARTITION_KEY, PARTITION_KEY_VALUES))
+TEST_ITEM = {'id': ITEM_ID}
+TEST_ITEM.update(PARTITION_KEY_ITEMS)
 
 L0 = "Default"
 L1 = "West US 3"
@@ -72,13 +81,28 @@ def emit(self, record):
     [[L1, L2], [L1], [L2]],
     # 6. Request excluded location not in preferred locations
     [[L1, L2], [L1, L2], [L3]],
-    # 7. Empty excluded locations, remove all client excluded locations
+    # 7. Empty excluded locations, remove all client level excluded locations
     [[L1, L2], [L1, L2], []],
 ]
 
-ALL_INPUT_TEST_DATA = CLIENT_ONLY_TEST_DATA + CLIENT_AND_REQUEST_TEST_DATA
-# ALL_INPUT_TEST_DATA = CLIENT_ONLY_TEST_DATA
-# ALL_INPUT_TEST_DATA = CLIENT_AND_REQUEST_TEST_DATA
+def set_test_data_type(test_data_type):
+    global TEST_DATA_TYPE
+    TEST_DATA_TYPE = test_data_type
+
+def get_test_data_with_expected_output(_client_only_output_data, _client_and_request_output_data):
+    if TEST_DATA_TYPE == TestDataType.CLIENT_ONLY_TESTS:
+        all_input_test_data = CLIENT_ONLY_TEST_DATA
+        all_output_data = _client_only_output_data
+    elif TEST_DATA_TYPE == TestDataType.CLIENT_AND_REQUEST_TESTS:
+        all_input_test_data = CLIENT_AND_REQUEST_TEST_DATA
+        all_output_data = _client_and_request_output_data
+    else:
+        all_input_test_data = CLIENT_ONLY_TEST_DATA + CLIENT_AND_REQUEST_TEST_DATA
+        all_output_data = _client_only_output_data + _client_and_request_output_data
+
+    all_test_data = [input_data + [output_data] for input_data, output_data in
+                     zip(all_input_test_data, all_output_data)]
+    return all_test_data
 
 def read_item_test_data():
     client_only_output_data = [
@@ -97,61 +121,14 @@ def read_item_test_data():
         [L1],  # 6
         [L1],  # 7
     ]
-    all_output_test_data = client_only_output_data + client_and_request_output_data
-
-    all_test_data = [input_data + [output_data] for input_data, output_data in zip(ALL_INPUT_TEST_DATA, all_output_test_data)]
-    return all_test_data
+    return get_test_data_with_expected_output(client_only_output_data, client_and_request_output_data)
 
-def query_items_change_feed_test_data():
-    client_only_output_data = [
-        [L1, L1, L1, L1],   #0
-        [L2, L2, L2, L2],   #1
-        [L1, L1, L1, L1],   #2
-        [L1, L1, L1, L1]    #3
-    ]
-    client_and_request_output_data = [
-        [L1, L1, L2, L2],   #0
-        [L2, L2, L2, L2],   #1
-        [L1, L1, L2, L2],   #2
-        [L2, L2, L1, L1],   #3
-        [L1, L1, L1, L1],   #4
-        [L2, L2, L1, L1],   #5
-        [L1, L1, L1, L1],   #6
-        [L1, L1, L1, L1],   #7
-    ]
-    all_output_test_data = client_only_output_data + client_and_request_output_data
-
-    all_test_data = [input_data + [output_data] for input_data, output_data in zip(ALL_INPUT_TEST_DATA, all_output_test_data)]
-    return all_test_data
-
-def replace_item_test_data():
-    client_only_output_data = [
-        [L1, L1],   #0
-        [L2, L2],   #1
-        [L1, L0],   #2
-        [L1, L1]    #3
-    ]
-    client_and_request_output_data = [
-        [L2, L2],   #0
-        [L2, L2],   #1
-        [L2, L2],   #2
-        [L1, L0],   #3
-        [L1, L0],   #4
-        [L1, L1],   #5
-        [L1, L1],   #6
-        [L1, L1],   #7
-    ]
-    all_output_test_data = client_only_output_data + client_and_request_output_data
-
-    all_test_data = [input_data + [output_data] for input_data, output_data in zip(ALL_INPUT_TEST_DATA, all_output_test_data)]
-    return all_test_data
-
-def patch_item_test_data():
+def write_item_test_data():
     client_only_output_data = [
         [L1],   #0
         [L2],   #1
-        [L0],   #3
-        [L1]    #4
+        [L0],   #2
+        [L1]    #3
     ]
     client_and_request_output_data = [
         [L2],   #0
@@ -163,46 +140,41 @@ def patch_item_test_data():
         [L1],   #6
         [L1],   #7
     ]
-    all_output_test_data = client_only_output_data + client_and_request_output_data
+    return get_test_data_with_expected_output(client_only_output_data, client_and_request_output_data)
 
-    all_test_data = [input_data + [output_data] for input_data, output_data in zip(ALL_INPUT_TEST_DATA, all_output_test_data)]
-    return all_test_data
+def read_and_write_item_test_data():
+    read_item = read_item_test_data()
+    write_item = write_item_test_data()
 
-def _create_item_with_excluded_locations(container, body, excluded_locations):
+    # Combine the expected_locations of read and write item
+    for i in range(len(read_item)):
+        read_item[i][-1] += write_item[i][-1]
+    return read_item
+
+def create_item_with_excluded_locations(container, body, excluded_locations):
     if excluded_locations is None:
         container.create_item(body=body)
     else:
         container.create_item(body=body, excluded_locations=excluded_locations)
 
-@pytest.fixture(scope="class", autouse=True)
-def setup_and_teardown():
-    print("Setup: This runs before any tests")
-    logger = logging.getLogger("azure")
-    logger.addHandler(MOCK_HANDLER)
-    logger.setLevel(logging.DEBUG)
-
-    container = cosmos_client.CosmosClient(HOST, KEY).get_database_client(DATABASE_ID).get_container_client(CONTAINER_ID)
-    container.upsert_item(body=TEST_ITEM)
-
-    yield
-    # Code to run after tests
-    print("Teardown: This runs after all tests")
-
-def _init_container(preferred_locations, client_excluded_locations, multiple_write_locations = True):
-    client = cosmos_client.CosmosClient(HOST, KEY,
-                                      preferred_locations=preferred_locations,
-                                      excluded_locations=client_excluded_locations,
-                                      multiple_write_locations=multiple_write_locations)
+def init_container(preferred_locations, client_excluded_locations, multiple_write_locations = True):
+    client = CosmosClient(HOST, KEY,
+                          preferred_locations=preferred_locations,
+                          excluded_locations=client_excluded_locations,
+                          multiple_write_locations=multiple_write_locations)
     db = client.get_database_client(DATABASE_ID)
     container = db.get_container_client(CONTAINER_ID)
     MOCK_HANDLER.reset()
 
     return client, db, container
 
-def _verify_endpoint(messages, client, expected_locations):
+def verify_endpoint(messages, client, expected_locations, multiple_write_locations = True):
+    if not multiple_write_locations:
+        expected_locations[-1] = L1
+
     # get mapping for locations
     location_mapping = (client.client_connection._global_endpoint_manager.
-                        location_cache.account_locations_by_write_regional_routing_context)
+                        location_cache.account_locations_by_write_endpoints)
     default_endpoint = (client.client_connection._global_endpoint_manager.
                         location_cache.default_regional_routing_context.get_primary())
 
@@ -210,18 +182,33 @@ def _verify_endpoint(messages, client, expected_locations):
     req_urls = [url.replace("Request URL: '", "") for url in messages if 'Request URL:' in url]
 
     # get location
-    actual_locations = []
+    actual_locations = set()
     for req_url in req_urls:
         if req_url.startswith(default_endpoint):
-            actual_locations.append(L0)
+            actual_locations.add(L0)
         else:
             for endpoint in location_mapping:
                 if req_url.startswith(endpoint):
                     location = location_mapping[endpoint]
-                    actual_locations.append(location)
+                    actual_locations.add(location)
                     break
 
-    assert actual_locations == expected_locations
+    assert actual_locations == set(expected_locations)
+
+@pytest.fixture(scope="class", autouse=True)
+def setup_and_teardown():
+    print("Setup: This runs before any tests")
+    logger = logging.getLogger("azure")
+    logger.addHandler(MOCK_HANDLER)
+    logger.setLevel(logging.DEBUG)
+
+    container = CosmosClient(HOST, KEY).get_database_client(DATABASE_ID).get_container_client(CONTAINER_ID)
+    container.upsert_item(body=TEST_ITEM)
+    # Waiting some time for the new items to be replicated to other regions
+    time.sleep(3)
+    yield
+    # Code to run after tests
+    print("Teardown: This runs after all tests")
 
 @pytest.mark.cosmosMultiRegion
 class TestExcludedLocations:
@@ -231,16 +218,16 @@ def test_read_item(self, test_data):
         preferred_locations, client_excluded_locations, request_excluded_locations, expected_locations = test_data
 
         # Client setup
-        client, db, container = _init_container(preferred_locations, client_excluded_locations)
+        client, db, container = init_container(preferred_locations, client_excluded_locations)
 
         # API call: read_item
         if request_excluded_locations is None:
-            container.read_item(ITEM_ID, ITEM_PK_VALUE)
+            container.read_item(item=ITEM_ID, partition_key=PARTITION_KEY_VALUES)
         else:
-            container.read_item(ITEM_ID, ITEM_PK_VALUE, excluded_locations=request_excluded_locations)
+            container.read_item(item=ITEM_ID, partition_key=PARTITION_KEY_VALUES, excluded_locations=request_excluded_locations)
 
         # Verify endpoint locations
-        _verify_endpoint(MOCK_HANDLER.messages, client, expected_locations)
+        verify_endpoint(MOCK_HANDLER.messages, client, expected_locations)
 
     @pytest.mark.parametrize('test_data', read_item_test_data())
     def test_read_all_items(self, test_data):
@@ -248,7 +235,7 @@ def test_read_all_items(self, test_data):
         preferred_locations, client_excluded_locations, request_excluded_locations, expected_locations = test_data
 
         # Client setup
-        client, db, container = _init_container(preferred_locations, client_excluded_locations)
+        client, db, container = init_container(preferred_locations, client_excluded_locations)
 
         # API call: read_all_items
         if request_excluded_locations is None:
@@ -257,52 +244,70 @@ def test_read_all_items(self, test_data):
             list(container.read_all_items(excluded_locations=request_excluded_locations))
 
         # Verify endpoint locations
-        _verify_endpoint(MOCK_HANDLER.messages, client, expected_locations)
+        verify_endpoint(MOCK_HANDLER.messages, client, expected_locations)
 
     @pytest.mark.parametrize('test_data', read_item_test_data())
-    def test_query_items(self, test_data):
+    def test_query_items_with_partition_key(self, test_data):
         # Init test variables
         preferred_locations, client_excluded_locations, request_excluded_locations, expected_locations = test_data
 
         # Client setup and create an item
-        client, db, container = _init_container(preferred_locations, client_excluded_locations)
+        client, db, container = init_container(preferred_locations, client_excluded_locations)
 
         # API call: query_items
+        query = 'select * from c'
         if request_excluded_locations is None:
-            list(container.query_items(None))
+            list(container.query_items(query, partition_key=PREFIX_PARTITION_KEY))
         else:
-            list(container.query_items(None, excluded_locations=request_excluded_locations))
+            list(container.query_items(query, partition_key=PREFIX_PARTITION_KEY, excluded_locations=request_excluded_locations))
 
         # Verify endpoint locations
-        _verify_endpoint(MOCK_HANDLER.messages, client, expected_locations)
+        verify_endpoint(MOCK_HANDLER.messages, client, expected_locations)
 
-    @pytest.mark.parametrize('test_data', query_items_change_feed_test_data())
-    def test_query_items_change_feed(self, test_data):
+    @pytest.mark.parametrize('test_data', read_item_test_data())
+    def test_query_items_with_query_plan(self, test_data):
         # Init test variables
         preferred_locations, client_excluded_locations, request_excluded_locations, expected_locations = test_data
 
+        # Client setup and create an item
+        client, db, container = init_container(preferred_locations, client_excluded_locations)
+
+        # API call: query_items
+        query = 'Select top 10 value count(c.id) from c'
+        if request_excluded_locations is None:
+            list(container.query_items(query, enable_cross_partition_query=True))
+            # list(container.query_items(query))
+        else:
+            list(container.query_items(query, enable_cross_partition_query=True, excluded_locations=request_excluded_locations))
+
+        # Verify endpoint locations
+        verify_endpoint(MOCK_HANDLER.messages, client, expected_locations)
+
+    @pytest.mark.parametrize('test_data', read_item_test_data())
+    def test_query_items_change_feed(self, test_data):
+        # Init test variables
+        preferred_locations, client_excluded_locations, request_excluded_locations, expected_locations = test_data
 
         # Client setup and create an item
-        client, db, container = _init_container(preferred_locations, client_excluded_locations)
+        client, db, container = init_container(preferred_locations, client_excluded_locations)
 
         # API call: query_items_change_feed
         if request_excluded_locations is None:
-            items = list(container.query_items_change_feed(start_time="Beginning"))
+            items = list(container.query_items_change_feed(start_time="Beginning", partition_key=PREFIX_PARTITION_KEY))
         else:
-            items = list(container.query_items_change_feed(start_time="Beginning", excluded_locations=request_excluded_locations))
+            items = list(container.query_items_change_feed(start_time="Beginning", partition_key=PREFIX_PARTITION_KEY, excluded_locations=request_excluded_locations))
 
         # Verify endpoint locations
-        _verify_endpoint(MOCK_HANDLER.messages, client, expected_locations)
-
+        verify_endpoint(MOCK_HANDLER.messages, client, expected_locations)
 
-    @pytest.mark.parametrize('test_data', replace_item_test_data())
+    @pytest.mark.parametrize('test_data', read_and_write_item_test_data())
     def test_replace_item(self, test_data):
         # Init test variables
         preferred_locations, client_excluded_locations, request_excluded_locations, expected_locations = test_data
 
         for multiple_write_locations in [True, False]:
             # Client setup and create an item
-            client, db, container = _init_container(preferred_locations, client_excluded_locations, multiple_write_locations)
+            client, db, container = init_container(preferred_locations, client_excluded_locations, multiple_write_locations)
 
             # API call: replace_item
             if request_excluded_locations is None:
@@ -311,135 +316,124 @@ def test_replace_item(self, test_data):
                 container.replace_item(ITEM_ID, body=TEST_ITEM, excluded_locations=request_excluded_locations)
 
             # Verify endpoint locations
-            if multiple_write_locations:
-                _verify_endpoint(MOCK_HANDLER.messages, client, expected_locations)
-            else:
-                _verify_endpoint(MOCK_HANDLER.messages, client, [expected_locations[0], L1])
+            verify_endpoint(MOCK_HANDLER.messages, client, expected_locations, multiple_write_locations)
 
-    @pytest.mark.parametrize('test_data', replace_item_test_data())
+    @pytest.mark.parametrize('test_data', read_and_write_item_test_data())
     def test_upsert_item(self, test_data):
         # Init test variables
         preferred_locations, client_excluded_locations, request_excluded_locations, expected_locations = test_data
 
         for multiple_write_locations in [True, False]:
             # Client setup and create an item
-            client, db, container = _init_container(preferred_locations, client_excluded_locations, multiple_write_locations)
+            client, db, container = init_container(preferred_locations, client_excluded_locations, multiple_write_locations)
 
             # API call: upsert_item
-            body = {'pk': 'pk', 'id': f'doc2-{str(uuid.uuid4())}'}
+            body = {'id': f'doc2-{str(uuid.uuid4())}'}
+            body.update(PARTITION_KEY_ITEMS)
             if request_excluded_locations is None:
                 container.upsert_item(body=body)
             else:
                 container.upsert_item(body=body, excluded_locations=request_excluded_locations)
 
             # get location from mock_handler
-            if multiple_write_locations:
-                _verify_endpoint(MOCK_HANDLER.messages, client, expected_locations)
-            else:
-                _verify_endpoint(MOCK_HANDLER.messages, client, [expected_locations[0], L1])
+            verify_endpoint(MOCK_HANDLER.messages, client, expected_locations, multiple_write_locations)
 
-    @pytest.mark.parametrize('test_data', replace_item_test_data())
+    @pytest.mark.parametrize('test_data', read_and_write_item_test_data())
     def test_create_item(self, test_data):
         # Init test variables
         preferred_locations, client_excluded_locations, request_excluded_locations, expected_locations = test_data
 
         for multiple_write_locations in [True, False]:
             # Client setup and create an item
-            client, db, container = _init_container(preferred_locations, client_excluded_locations, multiple_write_locations)
+            client, db, container = init_container(preferred_locations, client_excluded_locations, multiple_write_locations)
 
             # API call: create_item
-            body = {'pk': 'pk', 'id': f'doc2-{str(uuid.uuid4())}'}
-            _create_item_with_excluded_locations(container, body, request_excluded_locations)
+            body = {'id': f'doc2-{str(uuid.uuid4())}'}
+            body.update(PARTITION_KEY_ITEMS)
+            create_item_with_excluded_locations(container, body, request_excluded_locations)
 
-            # get location from mock_handler
-            if multiple_write_locations:
-                _verify_endpoint(MOCK_HANDLER.messages, client, expected_locations)
-            else:
-                _verify_endpoint(MOCK_HANDLER.messages, client, [expected_locations[0], L1])
+            # Single write
+            verify_endpoint(MOCK_HANDLER.messages, client, expected_locations, multiple_write_locations)
 
-    @pytest.mark.parametrize('test_data', patch_item_test_data())
+    @pytest.mark.parametrize('test_data', write_item_test_data())
     def test_patch_item(self, test_data):
         # Init test variables
         preferred_locations, client_excluded_locations, request_excluded_locations, expected_locations = test_data
 
         for multiple_write_locations in [True, False]:
             # Client setup and create an item
-            client, db, container = _init_container(preferred_locations, client_excluded_locations,
-                                                         multiple_write_locations)
+            client, db, container = init_container(preferred_locations, client_excluded_locations,
+                                                   multiple_write_locations)
 
             # API call: patch_item
             operations = [
                 {"op": "add", "path": "/test_data", "value": f'Data-{str(uuid.uuid4())}'},
             ]
             if request_excluded_locations is None:
-                container.patch_item(item=ITEM_ID, partition_key=ITEM_PK_VALUE,
+                container.patch_item(item=ITEM_ID, partition_key=PARTITION_KEY_VALUES,
                                      patch_operations=operations)
             else:
-                container.patch_item(item=ITEM_ID, partition_key=ITEM_PK_VALUE,
+                container.patch_item(item=ITEM_ID, partition_key=PARTITION_KEY_VALUES,
                                      patch_operations=operations,
                                      excluded_locations=request_excluded_locations)
 
             # get location from mock_handler
-            if multiple_write_locations:
-                _verify_endpoint(MOCK_HANDLER.messages, client, expected_locations)
-            else:
-                _verify_endpoint(MOCK_HANDLER.messages, client, [L1])
+            verify_endpoint(MOCK_HANDLER.messages, client, expected_locations, multiple_write_locations)
 
-    @pytest.mark.parametrize('test_data', patch_item_test_data())
+    @pytest.mark.parametrize('test_data', write_item_test_data())
     def test_execute_item_batch(self, test_data):
         # Init test variables
         preferred_locations, client_excluded_locations, request_excluded_locations, expected_locations = test_data
 
         for multiple_write_locations in [True, False]:
             # Client setup and create an item
-            client, db, container = _init_container(preferred_locations, client_excluded_locations,
-                                                         multiple_write_locations)
+            client, db, container = init_container(preferred_locations, client_excluded_locations,
+                                                   multiple_write_locations)
 
             # API call: execute_item_batch
             batch_operations = []
             for i in range(3):
-                batch_operations.append(("create", ({"id": f'Doc-{str(uuid.uuid4())}', PARTITION_KEY: ITEM_PK_VALUE},)))
+                body = {'id': f'doc2-{str(uuid.uuid4())}'}
+                body.update(PARTITION_KEY_ITEMS)
+                batch_operations.append(("create", (
+                    body,
+                )))
 
             if request_excluded_locations is None:
                 container.execute_item_batch(batch_operations=batch_operations,
-                                            partition_key=ITEM_PK_VALUE,)
+                                            partition_key=PARTITION_KEY_VALUES,)
             else:
                 container.execute_item_batch(batch_operations=batch_operations,
-                                            partition_key=ITEM_PK_VALUE,
+                                            partition_key=PARTITION_KEY_VALUES,
                                      excluded_locations=request_excluded_locations)
 
             # get location from mock_handler
-            if multiple_write_locations:
-                _verify_endpoint(MOCK_HANDLER.messages, client, expected_locations)
-            else:
-                _verify_endpoint(MOCK_HANDLER.messages, client, [L1])
+            verify_endpoint(MOCK_HANDLER.messages, client, expected_locations, multiple_write_locations)
 
-    @pytest.mark.parametrize('test_data', patch_item_test_data())
+    @pytest.mark.parametrize('test_data', write_item_test_data())
     def test_delete_item(self, test_data):
         # Init test variables
         preferred_locations, client_excluded_locations, request_excluded_locations, expected_locations = test_data
 
         for multiple_write_locations in [True, False]:
             # Client setup
-            client, db, container = _init_container(preferred_locations, client_excluded_locations, multiple_write_locations)
+            client, db, container = init_container(preferred_locations, client_excluded_locations, multiple_write_locations)
 
             # create before delete
             item_id = f'doc2-{str(uuid.uuid4())}'
-            body = {PARTITION_KEY: ITEM_PK_VALUE, 'id': item_id}
-            _create_item_with_excluded_locations(container, body, request_excluded_locations)
+            body = {'id': item_id}
+            body.update(PARTITION_KEY_ITEMS)
+            create_item_with_excluded_locations(container, body, request_excluded_locations)
             MOCK_HANDLER.reset()
 
             # API call: delete_item
             if request_excluded_locations is None:
-                container.delete_item(item_id, ITEM_PK_VALUE)
+                container.delete_item(item_id, PARTITION_KEY_VALUES)
             else:
-                container.delete_item(item_id, ITEM_PK_VALUE, excluded_locations=request_excluded_locations)
+                container.delete_item(item_id, PARTITION_KEY_VALUES, excluded_locations=request_excluded_locations)
 
             # Verify endpoint locations
-            if multiple_write_locations:
-                _verify_endpoint(MOCK_HANDLER.messages, client, expected_locations)
-            else:
-                _verify_endpoint(MOCK_HANDLER.messages, client, [L1])
+            verify_endpoint(MOCK_HANDLER.messages, client, expected_locations, multiple_write_locations)
 
 if __name__ == "__main__":
     unittest.main()
diff --git a/sdk/cosmos/azure-cosmos/tests/test_excluded_locations_async.py b/sdk/cosmos/azure-cosmos/tests/test_excluded_locations_async.py
index 11ababfdfafd..db85ebd08121 100644
--- a/sdk/cosmos/azure-cosmos/tests/test_excluded_locations_async.py
+++ b/sdk/cosmos/azure-cosmos/tests/test_excluded_locations_async.py
@@ -2,6 +2,7 @@
 # Copyright (c) Microsoft Corporation. All rights reserved.
 
 import logging
+import time
 import unittest
 import uuid
 import test_config
@@ -9,8 +10,9 @@
 import pytest_asyncio
 
 from azure.cosmos.aio import CosmosClient
-from azure.cosmos.partition_key import PartitionKey
-from test_excluded_locations import _verify_endpoint
+from test_excluded_locations import (TestDataType, set_test_data_type,
+                                     read_item_test_data, write_item_test_data, read_and_write_item_test_data,
+                                     verify_endpoint)
 
 class MockHandler(logging.Handler):
     def __init__(self):
@@ -23,179 +25,38 @@ def reset(self):
     def emit(self, record):
         self.messages.append(record.msg)
 
+# Test configurations
 MOCK_HANDLER = MockHandler()
 CONFIG = test_config.TestConfig()
 HOST = CONFIG.host
 KEY = CONFIG.masterKey
 DATABASE_ID = CONFIG.TEST_DATABASE_ID
-CONTAINER_ID = CONFIG.TEST_SINGLE_PARTITION_CONTAINER_ID
-PARTITION_KEY = CONFIG.TEST_CONTAINER_PARTITION_KEY
+CONTAINER_ID = CONFIG.TEST_MULTI_PARTITION_PREFIX_PK_CONTAINER_ID
+PARTITION_KEY = CONFIG.TEST_CONTAINER_PREFIX_PARTITION_KEY
 ITEM_ID = 'doc1'
-ITEM_PK_VALUE = 'pk'
-TEST_ITEM = {'id': ITEM_ID, PARTITION_KEY: ITEM_PK_VALUE}
-
-L0 = "Default"
-L1 = "West US 3"
-L2 = "West US"
-L3 = "East US 2"
-
-# L0 = "Default"
-# L1 = "East US 2"
-# L2 = "East US"
-# L3 = "West US 2"
-
-CLIENT_ONLY_TEST_DATA = [
-    # preferred_locations, client_excluded_locations, excluded_locations_request
-    # 0. No excluded location
-    [[L1, L2], [], None],
-    # 1. Single excluded location
-    [[L1, L2], [L1], None],
-    # 2. Exclude all locations
-    [[L1, L2], [L1, L2], None],
-    # 3. Exclude a location not in preferred locations
-    [[L1, L2], [L3], None],
-]
-
-CLIENT_AND_REQUEST_TEST_DATA = [
-    # preferred_locations, client_excluded_locations, excluded_locations_request
-    # 0. No client excluded locations + a request excluded location
-    [[L1, L2], [], [L1]],
-    # 1. The same client and request excluded location
-    [[L1, L2], [L1], [L1]],
-    # 2. Less request excluded locations
-    [[L1, L2], [L1, L2], [L1]],
-    # 3. More request excluded locations
-    [[L1, L2], [L1], [L1, L2]],
-    # 4. All locations were excluded
-    [[L1, L2], [L1, L2], [L1, L2]],
-    # 5. No common excluded locations
-    [[L1, L2], [L1], [L2, L3]],
-    # 6. Request excluded location not in preferred locations
-    [[L1, L2], [L1, L2], [L3]],
-    # 7. Empty excluded locations, remove all client excluded locations
-    [[L1, L2], [L1, L2], []],
-]
-
-ALL_INPUT_TEST_DATA = CLIENT_ONLY_TEST_DATA + CLIENT_AND_REQUEST_TEST_DATA
-
-def read_item_test_data():
-    client_only_output_data = [
-        [L1],  # 0
-        [L2],  # 1
-        [L1],  # 2
-        [L1],  # 3
-    ]
-    client_and_request_output_data = [
-        [L2],  # 0
-        [L2],  # 1
-        [L2],  # 2
-        [L1],  # 3
-        [L1],  # 4
-        [L1],  # 5
-        [L1],  # 6
-        [L1],  # 7
-    ]
-    all_output_test_data = client_only_output_data + client_and_request_output_data
-
-    all_test_data = [input_data + [output_data] for input_data, output_data in zip(ALL_INPUT_TEST_DATA, all_output_test_data)]
-    return all_test_data
-
-def read_all_item_test_data():
-    client_only_output_data = [
-        [L1, L1],  # 0
-        [L2, L2],  # 1
-        [L1, L1],  # 2
-        [L1, L1],  # 3
-    ]
-    client_and_request_output_data = [
-        [L2, L2],  # 0
-        [L2, L2],  # 1
-        [L2, L2],  # 2
-        [L1, L1],  # 3
-        [L1, L1],  # 4
-        [L1, L1],  # 5
-        [L1, L1],  # 6
-        [L1, L1],  # 7
-    ]
-    all_output_test_data = client_only_output_data + client_and_request_output_data
-
-    all_test_data = [input_data + [output_data] for input_data, output_data in zip(ALL_INPUT_TEST_DATA, all_output_test_data)]
-    return all_test_data
-
-def query_items_change_feed_test_data():
-    client_only_output_data = [
-        [L1, L1, L1, L1],   #0
-        [L2, L2, L2, L2],   #1
-        [L1, L1, L1, L1],   #2
-        [L1, L1, L1, L1]    #3
-    ]
-    client_and_request_output_data = [
-        [L1, L2, L2, L2],   #0
-        [L2, L2, L2, L2],   #1
-        [L1, L2, L2, L2],   #2
-        [L2, L1, L1, L1],   #3
-        [L1, L1, L1, L1],   #4
-        [L2, L1, L1, L1],   #5
-        [L1, L1, L1, L1],   #6
-        [L1, L1, L1, L1],   #7
-    ]
-    all_output_test_data = client_only_output_data + client_and_request_output_data
-
-    all_test_data = [input_data + [output_data] for input_data, output_data in zip(ALL_INPUT_TEST_DATA, all_output_test_data)]
-    return all_test_data
-
-def replace_item_test_data():
-    client_only_output_data = [
-        [L1],   #0
-        [L2],   #1
-        [L0],   #2
-        [L1]    #3
-    ]
-    client_and_request_output_data = [
-        [L2],   #0
-        [L2],   #1
-        [L2],   #2
-        [L0],   #3
-        [L0],   #4
-        [L1],   #5
-        [L1],   #6
-        [L1],   #7
-    ]
-    all_output_test_data = client_only_output_data + client_and_request_output_data
-
-    all_test_data = [input_data + [output_data] for input_data, output_data in zip(ALL_INPUT_TEST_DATA, all_output_test_data)]
-    return all_test_data
-
-def patch_item_test_data():
-    client_only_output_data = [
-        [L1],   #0
-        [L2],   #1
-        [L0],   #2
-        [L1]    #3
-    ]
-    client_and_request_output_data = [
-        [L2],   #0
-        [L2],   #1
-        [L2],   #2
-        [L0],   #3
-        [L0],   #4
-        [L1],   #5
-        [L1],   #6
-        [L1],   #7
-    ]
-    all_output_test_data = client_only_output_data + client_and_request_output_data
-
-    all_test_data = [input_data + [output_data] for input_data, output_data in zip(ALL_INPUT_TEST_DATA, all_output_test_data)]
-    return all_test_data
-
-async def _create_item_with_excluded_locations(container, body, excluded_locations):
+PARTITION_KEY_VALUES = [f'value{i+1}' for i in range(len(PARTITION_KEY))]
+PREFIX_PARTITION_KEY = [PARTITION_KEY_VALUES[0]]
+PARTITION_KEY_ITEMS = dict(zip(PARTITION_KEY, PARTITION_KEY_VALUES))
+TEST_ITEM = {'id': ITEM_ID}
+TEST_ITEM.update(PARTITION_KEY_ITEMS)
+
+set_test_data_type(TestDataType.ALL_TESTS)
+
+async def create_item_with_excluded_locations_async(container, body, excluded_locations):
     if excluded_locations is None:
         await container.create_item(body=body)
     else:
         await container.create_item(body=body, excluded_locations=excluded_locations)
 
+async def init_container_async(client):
+    db = client.get_database_client(DATABASE_ID)
+    container = db.get_container_client(CONTAINER_ID)
+    MOCK_HANDLER.reset()
+
+    return db, container
+
 @pytest_asyncio.fixture(scope="class", autouse=True)
-async def setup_and_teardown():
+async def setup_and_teardown_async():
     print("Setup: This runs before any tests")
     logger = logging.getLogger("azure")
     logger.addHandler(MOCK_HANDLER)
@@ -204,24 +65,15 @@ async def setup_and_teardown():
     test_client = CosmosClient(HOST, KEY)
     container = test_client.get_database_client(DATABASE_ID).get_container_client(CONTAINER_ID)
     await container.upsert_item(body=TEST_ITEM)
-
+    # Waiting some time for the new items to be replicated to other regions
+    time.sleep(3)
     yield
-    await test_client.close()
-
-async def _init_container(preferred_locations, client_excluded_locations, multiple_write_locations = True):
-    client = CosmosClient(HOST, KEY,
-                          preferred_locations=preferred_locations,
-                          excluded_locations=client_excluded_locations,
-                          multiple_write_locations=multiple_write_locations)
-    db = await client.create_database_if_not_exists(DATABASE_ID)
-    container = await db.create_container_if_not_exists(CONTAINER_ID, PartitionKey(path='/' + PARTITION_KEY, kind='Hash'))
-    MOCK_HANDLER.reset()
-
-    return client, db, container
+    # Code to run after tests
+    print("Teardown: This runs after all tests")
 
 @pytest.mark.cosmosMultiRegion
 @pytest.mark.asyncio
-@pytest.mark.usefixtures("setup_and_teardown")
+@pytest.mark.usefixtures("setup_and_teardown_async")
 class TestExcludedLocations:
     @pytest.mark.parametrize('test_data', read_item_test_data())
     async def test_read_item(self, test_data):
@@ -229,215 +81,264 @@ async def test_read_item(self, test_data):
         preferred_locations, client_excluded_locations, request_excluded_locations, expected_locations = test_data
 
         # Client setup
-        client, db, container = await _init_container(preferred_locations, client_excluded_locations)
+        async with CosmosClient(HOST, KEY,
+                              preferred_locations=preferred_locations,
+                              excluded_locations=client_excluded_locations,
+                              multiple_write_locations=True) as client:
+            db, container = await init_container_async(client)
 
-        # API call: read_item
-        if request_excluded_locations is None:
-            await container.read_item(ITEM_ID, ITEM_PK_VALUE)
-        else:
-            await container.read_item(ITEM_ID, ITEM_PK_VALUE, excluded_locations=request_excluded_locations)
+            # API call: read_item
+            if request_excluded_locations is None:
+                await container.read_item(ITEM_ID, PARTITION_KEY_VALUES)
+            else:
+                await container.read_item(ITEM_ID, PARTITION_KEY_VALUES, excluded_locations=request_excluded_locations)
 
-        # Verify endpoint locations
-        _verify_endpoint(MOCK_HANDLER.messages, client, expected_locations)
+            # Verify endpoint locations
+            verify_endpoint(MOCK_HANDLER.messages, client, expected_locations)
 
-    @pytest.mark.parametrize('test_data', read_all_item_test_data())
+    @pytest.mark.parametrize('test_data', read_item_test_data())
     async def test_read_all_items(self, test_data):
         # Init test variables
         preferred_locations, client_excluded_locations, request_excluded_locations, expected_locations = test_data
 
         # Client setup
-        client, db, container = await _init_container(preferred_locations, client_excluded_locations)
+        async with CosmosClient(HOST, KEY,
+                                preferred_locations=preferred_locations,
+                                excluded_locations=client_excluded_locations,
+                                multiple_write_locations=True) as client:
+            db, container = await init_container_async(client)
 
-        # API call: read_all_items
-        if request_excluded_locations is None:
-            all_items = [item async for item in container.read_all_items()]
-        else:
-            all_items = [item async for item in container.read_all_items(excluded_locations=request_excluded_locations)]
+            # API call: read_all_items
+            if request_excluded_locations is None:
+                all_items = [item async for item in container.read_all_items()]
+            else:
+                all_items = [item async for item in container.read_all_items(excluded_locations=request_excluded_locations)]
 
-        # Verify endpoint locations
-        _verify_endpoint(MOCK_HANDLER.messages, client, expected_locations)
+            # Verify endpoint locations
+            verify_endpoint(MOCK_HANDLER.messages, client, expected_locations)
 
-    @pytest.mark.parametrize('test_data', read_all_item_test_data())
-    async def test_query_items(self, test_data):
+    @pytest.mark.parametrize('test_data', read_item_test_data())
+    async def test_query_items_with_partition_key(self, test_data):
         # Init test variables
         preferred_locations, client_excluded_locations, request_excluded_locations, expected_locations = test_data
 
-        # Client setup and create an item
-        client, db, container = await _init_container(preferred_locations, client_excluded_locations)
-
-        # API call: query_items
-        if request_excluded_locations is None:
-            all_items = [item async for item in container.query_items(None)]
-        else:
-            all_items = [item async for item in container.query_items(None, excluded_locations=request_excluded_locations)]
+        # Client setup
+        async with CosmosClient(HOST, KEY,
+                                preferred_locations=preferred_locations,
+                                excluded_locations=client_excluded_locations,
+                                multiple_write_locations=True) as client:
+            db, container = await init_container_async(client)
+
+            # API call: query_items
+            query = 'select * from c'
+            if request_excluded_locations is None:
+                all_items = [item async for item in container.query_items(query, partition_key=PREFIX_PARTITION_KEY)]
+            else:
+                all_items = [item async for item in container.query_items(query, partition_key=PREFIX_PARTITION_KEY, excluded_locations=request_excluded_locations)]
 
-        # Verify endpoint locations
-        _verify_endpoint(MOCK_HANDLER.messages, client, expected_locations)
+            # Verify endpoint locations
+            verify_endpoint(MOCK_HANDLER.messages, client, expected_locations)
 
-    @pytest.mark.parametrize('test_data', query_items_change_feed_test_data())
-    async def test_query_items_change_feed(self, test_data):
+    @pytest.mark.parametrize('test_data', read_item_test_data())
+    async def test_query_items_with_query_plan(self, test_data):
         # Init test variables
         preferred_locations, client_excluded_locations, request_excluded_locations, expected_locations = test_data
 
+        # Client setup
+        async with CosmosClient(HOST, KEY,
+                                preferred_locations=preferred_locations,
+                                excluded_locations=client_excluded_locations,
+                                multiple_write_locations=True) as client:
+            db, container = await init_container_async(client)
+
+            # API call: query_items
+            query = 'Select top 10 value count(c.id) from c'
+            if request_excluded_locations is None:
+                all_items = [item async for item in container.query_items(query)]
+            else:
+                all_items = [item async for item in container.query_items(query, excluded_locations=request_excluded_locations)]
 
-        # Client setup and create an item
-        client, db, container = await _init_container(preferred_locations, client_excluded_locations)
-
-        # API call: query_items_change_feed
-        if request_excluded_locations is None:
-            all_items = [item async for item in container.query_items_change_feed(start_time="Beginning")]
-        else:
-            all_items = [item async for item in container.query_items_change_feed(start_time="Beginning", excluded_locations=request_excluded_locations)]
-
-        # Verify endpoint locations
-        _verify_endpoint(MOCK_HANDLER.messages, client, expected_locations)
-
+            # Verify endpoint locations
+            verify_endpoint(MOCK_HANDLER.messages, client, expected_locations)
 
-    @pytest.mark.parametrize('test_data', replace_item_test_data())
-    async def test_replace_item(self, test_data):
+    @pytest.mark.parametrize('test_data', read_item_test_data())
+    async def test_query_items_change_feed(self, test_data):
         # Init test variables
         preferred_locations, client_excluded_locations, request_excluded_locations, expected_locations = test_data
 
-        for multiple_write_locations in [True, False]:
-            # Client setup and create an item
-            client, db, container = await _init_container(preferred_locations, client_excluded_locations, multiple_write_locations)
 
-            # API call: replace_item
+        # Client setup
+        async with CosmosClient(HOST, KEY,
+                                preferred_locations=preferred_locations,
+                                excluded_locations=client_excluded_locations,
+                                multiple_write_locations=True) as client:
+            db, container = await init_container_async(client)
+            # API call: query_items_change_feed
             if request_excluded_locations is None:
-                await container.replace_item(ITEM_ID, body=TEST_ITEM)
+                all_items = [item async for item in container.query_items_change_feed(start_time="Beginning", partition_key=PREFIX_PARTITION_KEY)]
             else:
-                await container.replace_item(ITEM_ID, body=TEST_ITEM, excluded_locations=request_excluded_locations)
+                all_items = [item async for item in container.query_items_change_feed(start_time="Beginning", partition_key=PREFIX_PARTITION_KEY, excluded_locations=request_excluded_locations)]
 
             # Verify endpoint locations
-            if multiple_write_locations:
-                _verify_endpoint(MOCK_HANDLER.messages, client, expected_locations)
-            else:
-                _verify_endpoint(MOCK_HANDLER.messages, client, [L1])
+            verify_endpoint(MOCK_HANDLER.messages, client, expected_locations)
 
-    @pytest.mark.parametrize('test_data', replace_item_test_data())
-    async def test_upsert_item(self, test_data):
+
+    @pytest.mark.parametrize('test_data', read_and_write_item_test_data())
+    async def test_replace_item(self, test_data):
         # Init test variables
         preferred_locations, client_excluded_locations, request_excluded_locations, expected_locations = test_data
 
         for multiple_write_locations in [True, False]:
-            # Client setup and create an item
-            client, db, container = await _init_container(preferred_locations, client_excluded_locations, multiple_write_locations)
-
-            # API call: upsert_item
-            body = {'pk': 'pk', 'id': f'doc2-{str(uuid.uuid4())}'}
-            if request_excluded_locations is None:
-                await container.upsert_item(body=body)
-            else:
-                await container.upsert_item(body=body, excluded_locations=request_excluded_locations)
-
-            # get location from mock_handler
-            if multiple_write_locations:
-                _verify_endpoint(MOCK_HANDLER.messages, client, expected_locations)
-            else:
-                _verify_endpoint(MOCK_HANDLER.messages, client, [L1])
+            # Client setup
+            async with CosmosClient(HOST, KEY,
+                                    preferred_locations=preferred_locations,
+                                    excluded_locations=client_excluded_locations,
+                                    multiple_write_locations=multiple_write_locations) as client:
+                db, container = await init_container_async(client)
+
+                # API call: replace_item
+                if request_excluded_locations is None:
+                    await container.replace_item(ITEM_ID, body=TEST_ITEM)
+                else:
+                    await container.replace_item(ITEM_ID, body=TEST_ITEM, excluded_locations=request_excluded_locations)
+
+                # Verify endpoint locations
+                verify_endpoint(MOCK_HANDLER.messages, client, expected_locations, multiple_write_locations)
+
+    @pytest.mark.parametrize('test_data', read_and_write_item_test_data())
+    async def test_upsert_item(self, test_data):
+        # Init test variables
+        preferred_locations, client_excluded_locations, request_excluded_locations, expected_locations = test_data
 
-    @pytest.mark.parametrize('test_data', replace_item_test_data())
+        for multiple_write_locations in [True, False]:
+            # Client setup
+            async with CosmosClient(HOST, KEY,
+                                    preferred_locations=preferred_locations,
+                                    excluded_locations=client_excluded_locations,
+                                    multiple_write_locations=multiple_write_locations) as client:
+                db, container = await init_container_async(client)
+
+                # API call: upsert_item
+                body = {'id': f'doc2-{str(uuid.uuid4())}'}
+                body.update(PARTITION_KEY_ITEMS)
+                if request_excluded_locations is None:
+                    await container.upsert_item(body=body)
+                else:
+                    await container.upsert_item(body=body, excluded_locations=request_excluded_locations)
+
+                # get location from mock_handler
+                verify_endpoint(MOCK_HANDLER.messages, client, expected_locations, multiple_write_locations)
+
+    @pytest.mark.parametrize('test_data', read_and_write_item_test_data())
     async def test_create_item(self, test_data):
         # Init test variables
         preferred_locations, client_excluded_locations, request_excluded_locations, expected_locations = test_data
 
         for multiple_write_locations in [True, False]:
-            # Client setup and create an item
-            client, db, container = await _init_container(preferred_locations, client_excluded_locations, multiple_write_locations)
+            # Client setup
+            async with CosmosClient(HOST, KEY,
+                                    preferred_locations=preferred_locations,
+                                    excluded_locations=client_excluded_locations,
+                                    multiple_write_locations=multiple_write_locations) as client:
+                db, container = await init_container_async(client)
 
-            # API call: create_item
-            body = {'pk': 'pk', 'id': f'doc2-{str(uuid.uuid4())}'}
-            await _create_item_with_excluded_locations(container, body, request_excluded_locations)
+                # API call: create_item
+                body = {'id': f'doc2-{str(uuid.uuid4())}'}
+                body.update(PARTITION_KEY_ITEMS)
+                await create_item_with_excluded_locations_async(container, body, request_excluded_locations)
 
-            # get location from mock_handler
-            if multiple_write_locations:
-                _verify_endpoint(MOCK_HANDLER.messages, client, expected_locations)
-            else:
-                _verify_endpoint(MOCK_HANDLER.messages, client, [L1])
+                # get location from mock_handler
+                verify_endpoint(MOCK_HANDLER.messages, client, expected_locations, multiple_write_locations)
 
-    @pytest.mark.parametrize('test_data', patch_item_test_data())
+    @pytest.mark.parametrize('test_data', write_item_test_data())
     async def test_patch_item(self, test_data):
         # Init test variables
         preferred_locations, client_excluded_locations, request_excluded_locations, expected_locations = test_data
 
         for multiple_write_locations in [True, False]:
-            # Client setup and create an item
-            client, db, container = await _init_container(preferred_locations, client_excluded_locations,
-                                                         multiple_write_locations)
-
-            # API call: patch_item
-            operations = [
-                {"op": "add", "path": "/test_data", "value": f'Data-{str(uuid.uuid4())}'},
-            ]
-            if request_excluded_locations is None:
-                await container.patch_item(item=ITEM_ID, partition_key=ITEM_PK_VALUE,
-                                     patch_operations=operations)
-            else:
-                await container.patch_item(item=ITEM_ID, partition_key=ITEM_PK_VALUE,
-                                     patch_operations=operations,
-                                     excluded_locations=request_excluded_locations)
-
-            # get location from mock_handler
-            if multiple_write_locations:
-                _verify_endpoint(MOCK_HANDLER.messages, client, expected_locations)
-            else:
-                _verify_endpoint(MOCK_HANDLER.messages, client, [L1])
-
-    @pytest.mark.parametrize('test_data', patch_item_test_data())
+            # Client setup
+            async with CosmosClient(HOST, KEY,
+                                    preferred_locations=preferred_locations,
+                                    excluded_locations=client_excluded_locations,
+                                    multiple_write_locations=multiple_write_locations) as client:
+                db, container = await init_container_async(client)
+
+                # API call: patch_item
+                operations = [
+                    {"op": "add", "path": "/test_data", "value": f'Data-{str(uuid.uuid4())}'},
+                ]
+                if request_excluded_locations is None:
+                    await container.patch_item(item=ITEM_ID, partition_key=PARTITION_KEY_VALUES,
+                                         patch_operations=operations)
+                else:
+                    await container.patch_item(item=ITEM_ID, partition_key=PARTITION_KEY_VALUES,
+                                         patch_operations=operations,
+                                         excluded_locations=request_excluded_locations)
+
+                # get location from mock_handler
+                verify_endpoint(MOCK_HANDLER.messages, client, expected_locations, multiple_write_locations)
+
+    @pytest.mark.parametrize('test_data', write_item_test_data())
     async def test_execute_item_batch(self, test_data):
         # Init test variables
         preferred_locations, client_excluded_locations, request_excluded_locations, expected_locations = test_data
 
         for multiple_write_locations in [True, False]:
-            # Client setup and create an item
-            client, db, container = await _init_container(preferred_locations, client_excluded_locations,
-                                                         multiple_write_locations)
-
-            # API call: execute_item_batch
-            batch_operations = []
-            for i in range(3):
-                batch_operations.append(("create", ({"id": f'Doc-{str(uuid.uuid4())}', PARTITION_KEY: ITEM_PK_VALUE},)))
-
-            if request_excluded_locations is None:
-                await container.execute_item_batch(batch_operations=batch_operations,
-                                            partition_key=ITEM_PK_VALUE,)
-            else:
-                await container.execute_item_batch(batch_operations=batch_operations,
-                                            partition_key=ITEM_PK_VALUE,
-                                     excluded_locations=request_excluded_locations)
-
-            # get location from mock_handler
-            if multiple_write_locations:
-                _verify_endpoint(MOCK_HANDLER.messages, client, expected_locations)
-            else:
-                _verify_endpoint(MOCK_HANDLER.messages, client, [L1])
-
-    @pytest.mark.parametrize('test_data', patch_item_test_data())
+            # Client setup
+            async with CosmosClient(HOST, KEY,
+                                    preferred_locations=preferred_locations,
+                                    excluded_locations=client_excluded_locations,
+                                    multiple_write_locations=multiple_write_locations) as client:
+                db, container = await init_container_async(client)
+
+                # API call: execute_item_batch
+                batch_operations = []
+                for i in range(3):
+                    body = {'id': f'doc2-{str(uuid.uuid4())}'}
+                    body.update(PARTITION_KEY_ITEMS)
+                    batch_operations.append(("create", (
+                        body,
+                    )))
+
+                if request_excluded_locations is None:
+                    await container.execute_item_batch(batch_operations=batch_operations,
+                                                partition_key=PARTITION_KEY_VALUES,)
+                else:
+                    await container.execute_item_batch(batch_operations=batch_operations,
+                                                partition_key=PARTITION_KEY_VALUES,
+                                         excluded_locations=request_excluded_locations)
+
+                # get location from mock_handler
+                verify_endpoint(MOCK_HANDLER.messages, client, expected_locations, multiple_write_locations)
+
+    @pytest.mark.parametrize('test_data', write_item_test_data())
     async def test_delete_item(self, test_data):
         # Init test variables
         preferred_locations, client_excluded_locations, request_excluded_locations, expected_locations = test_data
 
         for multiple_write_locations in [True, False]:
             # Client setup
-            client, db, container = await _init_container(preferred_locations, client_excluded_locations, multiple_write_locations)
-
-            # create before delete
-            item_id = f'doc2-{str(uuid.uuid4())}'
-            body = {PARTITION_KEY: ITEM_PK_VALUE, 'id': item_id}
-            await _create_item_with_excluded_locations(container, body, request_excluded_locations)
-            MOCK_HANDLER.reset()
-
-            # API call: delete_item
-            if request_excluded_locations is None:
-                await container.delete_item(item_id, ITEM_PK_VALUE)
-            else:
-                await container.delete_item(item_id, ITEM_PK_VALUE, excluded_locations=request_excluded_locations)
-
-            # Verify endpoint locations
-            if multiple_write_locations:
-                _verify_endpoint(MOCK_HANDLER.messages, client, expected_locations)
-            else:
-                _verify_endpoint(MOCK_HANDLER.messages, client, [L1])
+            async with CosmosClient(HOST, KEY,
+                                    preferred_locations=preferred_locations,
+                                    excluded_locations=client_excluded_locations,
+                                    multiple_write_locations=multiple_write_locations) as client:
+                db, container = await init_container_async(client)
+
+                # create before delete
+                item_id = f'doc2-{str(uuid.uuid4())}'
+                body = {'id': item_id}
+                body.update(PARTITION_KEY_ITEMS)
+                await create_item_with_excluded_locations_async(container, body, request_excluded_locations)
+                MOCK_HANDLER.reset()
+
+                # API call: delete_item
+                if request_excluded_locations is None:
+                    await container.delete_item(item_id, PARTITION_KEY_VALUES)
+                else:
+                    await container.delete_item(item_id, PARTITION_KEY_VALUES, excluded_locations=request_excluded_locations)
+
+                # Verify endpoint locations
+                verify_endpoint(MOCK_HANDLER.messages, client, expected_locations, multiple_write_locations)
 
 if __name__ == "__main__":
     unittest.main()
diff --git a/sdk/cosmos/azure-cosmos/tests/test_excluded_locations_emulator.py b/sdk/cosmos/azure-cosmos/tests/test_excluded_locations_emulator.py
index 375bcfc899d8..db787b7353b5 100644
--- a/sdk/cosmos/azure-cosmos/tests/test_excluded_locations_emulator.py
+++ b/sdk/cosmos/azure-cosmos/tests/test_excluded_locations_emulator.py
@@ -8,43 +8,50 @@
 from typing import Callable, List, Mapping, Any
 
 from azure.core.rest import HttpRequest
-from _fault_injection_transport import FaultInjectionTransport
+from _fault_injection_transport import FaultInjectionTransport, ERROR_WITH_COUNTER
 from azure.cosmos.container import ContainerProxy
-from test_excluded_locations import L1, L2, CLIENT_ONLY_TEST_DATA, CLIENT_AND_REQUEST_TEST_DATA
+from test_excluded_locations import (L1, L2, read_item_test_data,
+                                     TestDataType, set_test_data_type,
+                                     get_test_data_with_expected_output)
 from test_fault_injection_transport import TestFaultInjectionTransport
+from azure.cosmos.exceptions import CosmosHttpResponseError
+from azure.cosmos.http_constants import ResourceType
 
 CONFIG = test_config.TestConfig()
-
-L1_URL = test_config.TestConfig.local_host
+HOST = CONFIG.host
+KEY = CONFIG.masterKey
+DATABASE_ID = CONFIG.TEST_DATABASE_ID
+SINGLE_PARTITION_CONTAINER_ID = CONFIG.TEST_SINGLE_PARTITION_CONTAINER_ID
+SINGLE_PARTITION_PREFIX_PK_CONTAINER_ID = CONFIG.TEST_SINGLE_PARTITION_PREFIX_PK_CONTAINER_ID
+L1_URL = CONFIG.local_host
 L2_URL = L1_URL.replace("localhost", "127.0.0.1")
 URL_TO_LOCATIONS = {
     L1_URL: L1,
     L2_URL: L2
 }
 
-ALL_INPUT_TEST_DATA = CLIENT_ONLY_TEST_DATA + CLIENT_AND_REQUEST_TEST_DATA
+set_test_data_type(TestDataType.ALL_TESTS)
 
-def delete_all_items_by_partition_key_test_data() -> List[str]:
+# The output data is the number of retries that happened with different timeout region cases
+# [timeout L1, timeout L2]
+def metadata_read_with_excluded_locations_test_data() -> List[str]:
     client_only_output_data = [
-        L1,   #0
-        L2,   #1
-        L1,   #3
-        L1    #4
+        [1, 0],   #0
+        [0, 1],   #1
+        [1, 0],   #3
+        [1, 0]    #4
     ]
     client_and_request_output_data = [
-        L2,   #0
-        L2,   #1
-        L2,   #2
-        L1,   #3
-        L1,   #4
-        L1,   #5
-        L1,   #6
-        L1,   #7
+        [0, 1],   #0
+        [0, 1],   #1
+        [0, 1],   #2
+        [1, 0],   #3
+        [1, 0],   #4
+        [1, 0],   #5
+        [1, 0],   #6
+        [1, 0],   #7
     ]
-    all_output_test_data = client_only_output_data + client_and_request_output_data
-
-    all_test_data = [input_data + [output_data] for input_data, output_data in zip(ALL_INPUT_TEST_DATA, all_output_test_data)]
-    return all_test_data
+    return get_test_data_with_expected_output(client_only_output_data, client_and_request_output_data)
 
 def get_location(
         initialized_objects: Mapping[str, Any],
@@ -64,7 +71,7 @@ def get_location(
 @pytest.mark.unittest
 @pytest.mark.cosmosEmulator
 class TestExcludedLocationsEmulator:
-    @pytest.mark.parametrize('test_data', delete_all_items_by_partition_key_test_data())
+    @pytest.mark.parametrize('test_data', read_item_test_data())
     def test_delete_all_items_by_partition_key(self: "TestExcludedLocationsEmulator", test_data: List[List[str]]):
         # Init test variables
         preferred_locations, client_excluded_locations, request_excluded_locations, expected_location = test_data
@@ -90,10 +97,10 @@ def test_delete_all_items_by_partition_key(self: "TestExcludedLocationsEmulator"
             # Create client
             initialized_objects = TestFaultInjectionTransport.setup_method_with_custom_transport(
                 custom_transport,
-                default_endpoint=CONFIG.host,
-                key=CONFIG.masterKey,
-                database_id=CONFIG.TEST_DATABASE_ID,
-                container_id=CONFIG.TEST_SINGLE_PARTITION_CONTAINER_ID,
+                default_endpoint=HOST,
+                key=KEY,
+                database_id=DATABASE_ID,
+                container_id=SINGLE_PARTITION_CONTAINER_ID,
                 preferred_locations=preferred_locations,
                 excluded_locations=client_excluded_locations,
                 multiple_write_locations=multiple_write_locations,
@@ -114,9 +121,71 @@ def test_delete_all_items_by_partition_key(self: "TestExcludedLocationsEmulator"
             # Verify endpoint locations
             actual_location = get_location(initialized_objects)
             if multiple_write_locations:
-                assert actual_location == expected_location
+                assert actual_location == expected_location[0]
             else:
                 assert actual_location == L1
 
+    @pytest.mark.parametrize('test_data', metadata_read_with_excluded_locations_test_data())
+    def test_metadata_read_with_excluded_locations(self: "TestExcludedLocationsEmulator", test_data: List[List[str]]):
+        # Init test variables
+        preferred_locations, client_excluded_locations, request_excluded_locations, expected_counts = test_data
+        target_urls = [L1_URL, L2_URL]
+        expected_locations = [L2, L1]
+        for target_url, expected_location, expected_count in zip(target_urls, expected_locations, expected_counts):
+            # Inject topology transformation that would make Emulator look like a multiple write region account
+            # with two read regions
+            custom_transport = FaultInjectionTransport()
+            is_get_account_predicate: Callable[[HttpRequest], bool] = lambda \
+                    r: FaultInjectionTransport.predicate_is_database_account_call(r)
+            emulator_as_multi_write_region_account_transformation = \
+                lambda r, inner: FaultInjectionTransport.transform_topology_mwr(
+                    first_region_name=L1,
+                    second_region_name=L2,
+                    inner=inner,
+                    first_region_url=L1_URL,
+                    second_region_url=L2_URL,
+                )
+            custom_transport.add_response_transformation(
+                is_get_account_predicate,
+                emulator_as_multi_write_region_account_transformation)
+
+            # Inject rule to simulate request timeout in target region
+            is_request_to_target_region: Callable[[HttpRequest], bool] = lambda \
+                    r: (FaultInjectionTransport.predicate_targets_region(r, target_url) and
+                        FaultInjectionTransport.predicate_is_resource_type(r, ResourceType.Collection) and
+                        not FaultInjectionTransport.predicate_is_write_operation(r, target_url))
+            fault_factory = lambda r: custom_transport.error_with_counter(
+                CosmosHttpResponseError(
+                    status_code=408,
+                    message="Request Time Out Error.")
+            )
+            custom_transport.add_fault(is_request_to_target_region, fault_factory)
+
+            # Create client
+            initialized_objects = TestFaultInjectionTransport.setup_method_with_custom_transport(
+                custom_transport,
+                default_endpoint=HOST,
+                key=KEY,
+                database_id=DATABASE_ID,
+                container_id=SINGLE_PARTITION_PREFIX_PK_CONTAINER_ID,
+                preferred_locations=preferred_locations,
+                excluded_locations=client_excluded_locations,
+                multiple_write_locations=True,
+            )
+            container: ContainerProxy = initialized_objects["col"]
+
+            custom_transport.reset_counters()
+            if request_excluded_locations is None:
+                container.read()
+            else:
+                container.read(excluded_locations=request_excluded_locations)
+
+            # Verify endpoint locations
+            actual_location = get_location(initialized_objects)
+            assert actual_location == expected_location
+            actual_count = custom_transport.counters[ERROR_WITH_COUNTER]
+            assert actual_count == expected_count
+
+
 if __name__ == "__main__":
     unittest.main()
diff --git a/sdk/cosmos/azure-cosmos/tests/test_excluded_locations_emulator_async.py b/sdk/cosmos/azure-cosmos/tests/test_excluded_locations_emulator_async.py
index c24c2f13c9f7..b00c1b54a768 100644
--- a/sdk/cosmos/azure-cosmos/tests/test_excluded_locations_emulator_async.py
+++ b/sdk/cosmos/azure-cosmos/tests/test_excluded_locations_emulator_async.py
@@ -1,49 +1,48 @@
 # The MIT License (MIT)
 # Copyright (c) Microsoft Corporation. All rights reserved.
 
+import asyncio
 import unittest
 import uuid
+
+from azure.cosmos.aio import CosmosClient
+
 import test_config
 import pytest
-from typing import Callable, List, Mapping, Any
+from typing import Callable, List
 
+from azure.core.rest import HttpRequest
 from azure.core.pipeline.transport import AioHttpTransport
-from _fault_injection_transport_async import FaultInjectionTransportAsync
+from _fault_injection_transport_async import FaultInjectionTransportAsync, ERROR_WITH_COUNTER
 from azure.cosmos.aio._container import ContainerProxy
-from test_excluded_locations import L1, L2, CLIENT_ONLY_TEST_DATA, CLIENT_AND_REQUEST_TEST_DATA
-from test_excluded_locations_emulator import L1_URL, L2_URL, get_location
+from test_excluded_locations import (L1, L2, read_item_test_data,
+                                     TestDataType, set_test_data_type)
+from test_excluded_locations_emulator import (L1_URL, L2_URL,
+                                              get_location,
+                                              metadata_read_with_excluded_locations_test_data)
 from test_fault_injection_transport_async import TestFaultInjectionTransportAsync
+from azure.cosmos.exceptions import CosmosHttpResponseError
+from azure.cosmos.http_constants import ResourceType
 
 CONFIG = test_config.TestConfig()
+HOST = CONFIG.host
+KEY = CONFIG.masterKey
+DATABASE_ID = CONFIG.TEST_DATABASE_ID
+SINGLE_PARTITION_CONTAINER_ID = CONFIG.TEST_SINGLE_PARTITION_CONTAINER_ID
+SINGLE_PARTITION_PREFIX_PK_CONTAINER_ID = CONFIG.TEST_SINGLE_PARTITION_PREFIX_PK_CONTAINER_ID
+
+set_test_data_type(TestDataType.ALL_TESTS)
+
+async def init_container(client, db_id, container_id):
+    db = client.get_database_client(db_id)
+    container = db.get_container_client(container_id)
 
-ALL_INPUT_TEST_DATA = CLIENT_ONLY_TEST_DATA + CLIENT_AND_REQUEST_TEST_DATA
-
-def delete_all_items_by_partition_key_test_data() -> List[str]:
-    client_only_output_data = [
-        L1,   #0
-        L2,   #1
-        L1,   #3
-        L1    #4
-    ]
-    client_and_request_output_data = [
-        L2,   #0
-        L2,   #1
-        L2,   #2
-        L1,   #3
-        L1,   #4
-        L1,   #5
-        L1,   #6
-        L1,   #7
-    ]
-    all_output_test_data = client_only_output_data + client_and_request_output_data
-
-    all_test_data = [input_data + [output_data] for input_data, output_data in zip(ALL_INPUT_TEST_DATA, all_output_test_data)]
-    return all_test_data
+    return db, container
 
 @pytest.mark.cosmosEmulator
 @pytest.mark.asyncio
 class TestExcludedLocationsEmulatorAsync:
-    @pytest.mark.parametrize('test_data', delete_all_items_by_partition_key_test_data())
+    @pytest.mark.parametrize('test_data', read_item_test_data())
     async def test_delete_all_items_by_partition_key(self: "TestExcludedLocationsEmulatorAsync", test_data: List[List[str]]):
         # Init test variables
         preferred_locations, client_excluded_locations, request_excluded_locations, expected_location = test_data
@@ -68,10 +67,10 @@ async def test_delete_all_items_by_partition_key(self: "TestExcludedLocationsEmu
             # Create client
             initialized_objects = await TestFaultInjectionTransportAsync.setup_method_with_custom_transport(
                 custom_transport,
-                default_endpoint=CONFIG.host,
-                key=CONFIG.masterKey,
-                database_id=CONFIG.TEST_DATABASE_ID,
-                container_id=CONFIG.TEST_SINGLE_PARTITION_CONTAINER_ID,
+                default_endpoint=HOST,
+                key=KEY,
+                database_id=DATABASE_ID,
+                container_id=SINGLE_PARTITION_CONTAINER_ID,
                 preferred_locations=preferred_locations,
                 excluded_locations=client_excluded_locations,
                 multiple_write_locations=multiple_write_locations,
@@ -92,9 +91,67 @@ async def test_delete_all_items_by_partition_key(self: "TestExcludedLocationsEmu
             # Verify endpoint locations
             actual_location = get_location(initialized_objects)
             if multiple_write_locations:
-                assert actual_location == expected_location
+                assert actual_location == expected_location[0]
             else:
                 assert actual_location == L1
 
+    @pytest.mark.parametrize('test_data', metadata_read_with_excluded_locations_test_data())
+    async def test_metadata_read_with_excluded_locations(self: "TestExcludedLocationsEmulatorAsync", test_data: List[List[str]]):
+        # Init test variables
+        preferred_locations, client_excluded_locations, request_excluded_locations, expected_counts = test_data
+        target_urls = [L1_URL, L2_URL]
+        expected_locations = [L2, L1]
+        for target_url, expected_location, expected_count in zip(target_urls, expected_locations, expected_counts):
+            # Inject topology transformation that would make Emulator look like a multiple write region account
+            # with two read regions
+            custom_transport = FaultInjectionTransportAsync()
+            is_get_account_predicate: Callable[[AioHttpTransport], bool] = lambda \
+                    r: FaultInjectionTransportAsync.predicate_is_database_account_call(r)
+            emulator_as_multi_write_region_account_transformation = \
+                lambda r, inner: FaultInjectionTransportAsync.transform_topology_mwr(
+                    first_region_name=L1,
+                    first_region_url=L1_URL,
+                    inner=inner,
+                    second_region_name=L2,
+                    second_region_url=L2_URL)
+            custom_transport.add_response_transformation(
+                is_get_account_predicate,
+                emulator_as_multi_write_region_account_transformation)
+
+            # Inject rule to simulate request timeout in target region
+            is_request_to_target_region: Callable[[HttpRequest], bool] = lambda \
+                    r: (FaultInjectionTransportAsync.predicate_targets_region(r, target_url) and
+                        FaultInjectionTransportAsync.predicate_is_resource_type(r, ResourceType.Collection) and
+                        not FaultInjectionTransportAsync.predicate_is_write_operation(r, target_url))
+            fault_factory = lambda r: asyncio.create_task(custom_transport.error_with_counter(
+                CosmosHttpResponseError(
+                    status_code=408,
+                    message="Request Time Out Error.")
+            ))
+            custom_transport.add_fault(is_request_to_target_region, fault_factory)
+
+
+            # Create client
+            async with CosmosClient(HOST, KEY,
+                                    consistency_level="Session",
+                                    transport=custom_transport,
+                                    # logger=logger, enable_diagnostics_logging=True,
+                                    preferred_locations=preferred_locations,
+                                    excluded_locations=client_excluded_locations,
+                                    multiple_write_locations=True) as client:
+                db, container = await init_container(client, DATABASE_ID, SINGLE_PARTITION_PREFIX_PK_CONTAINER_ID)
+
+                await custom_transport.reset_counters()
+                if request_excluded_locations is None:
+                    await container.read()
+                else:
+                    await container.read(excluded_locations=request_excluded_locations)
+
+                # Verify endpoint locations
+                actual_location = get_location({"client": client})
+                assert actual_location == expected_location
+                actual_count = custom_transport.counters[ERROR_WITH_COUNTER]
+                assert actual_count == expected_count
+
 if __name__ == "__main__":
     unittest.main()
diff --git a/sdk/cosmos/azure-cosmos/tests/test_location_cache.py b/sdk/cosmos/azure-cosmos/tests/test_location_cache.py
index 3b804ee37a3c..b619339525d7 100644
--- a/sdk/cosmos/azure-cosmos/tests/test_location_cache.py
+++ b/sdk/cosmos/azure-cosmos/tests/test_location_cache.py
@@ -257,23 +257,6 @@ def test_set_excluded_locations_for_requests(self):
         read_doc_endpoint = [regional_routing_contexts.get_primary() for regional_routing_contexts in read_regional_routing_contexts]
         assert read_doc_endpoint == expected_read_endpoints
 
-
-        # Test setting excluded locations with invalid resource types
-        expected_excluded_locations = None
-        for resource_type in [ResourceType.Offer, ResourceType.Conflict]:
-            options: Mapping[str, Any] = {"excludedLocations": [location1_name]}
-            read_doc_request = RequestObject(resource_type, _OperationType.Create)
-            read_doc_request.set_excluded_location_from_options(options)
-            actual_excluded_locations = read_doc_request.excluded_locations
-            assert actual_excluded_locations == expected_excluded_locations
-
-            expected_read_endpoints = [location1_endpoint]
-            read_regional_routing_contexts = location_cache._get_applicable_read_regional_routing_contexts(read_doc_request)
-            read_doc_endpoint = [regional_routing_contexts.get_primary() for regional_routing_contexts in read_regional_routing_contexts]
-            assert read_doc_endpoint == expected_read_endpoints
-
-
-
         # Test setting excluded locations with None value
         expected_error_message = ("Excluded locations cannot be None. "
                                   "If you want to remove all excluded locations, try passing an empty list.")
diff --git a/sdk/cosmos/live-platform-matrix.json b/sdk/cosmos/live-platform-matrix.json
index 347c166f869c..f9418a63aa9d 100644
--- a/sdk/cosmos/live-platform-matrix.json
+++ b/sdk/cosmos/live-platform-matrix.json
@@ -83,10 +83,10 @@
     },
     {
       "WindowsConfig": {
-        "Windows2022_38": {
+        "Windows2022_39": {
           "OSVmImage": "env:WINDOWSVMIMAGE",
           "Pool": "env:WINDOWSPOOL",
-          "PythonVersion": "3.8",
+          "PythonVersion": "3.9",
           "CoverageArg": "--disablecov",
           "TestSamples": "false",
           "TestMarkArgument": "cosmosMultiRegion"


Reviews:
user: login: copilot-pull-request-reviewer[bot]
body: ## Pull Request Overview

This PR enables request‐level excluded locations for metadata API calls in order to re‐route calls to non‐excluded locations and improve latency management. Key changes include updating live platform configuration, extending tests to validate metadata calls with excluded locations, and adjusting internal components (e.g. fault injection transport, location cache, routing providers) to handle excluded locations correctly.

### Reviewed Changes

Copilot reviewed 20 out of 20 changed files in this pull request and generated no comments.

<details>
<summary>Show a summary per file</summary>

| File | Description |
| ---- | ----------- |
| sdk/cosmos/live-platform-matrix.json | Updated Windows image version and Python version for metadata tests. |
| sdk/cosmos/azure-cosmos/tests/* | Updated tests to include request‐level excluded locations and revised client initialization with prefix partition keys. |
| sdk/cosmos/azure-cosmos/tests/_fault_injection_transport_async.py | Updated fault injection transport for async operations. |
| sdk/cosmos/azure-cosmos/tests/_fault_injection_transport.py | Updated fault injection transport for synchronous operations. |
| sdk/cosmos/azure-cosmos/tests/test_config.py | Added configuration for multi-partition container with prefix partition keys. |
| sdk/cosmos/azure-cosmos/azure/cosmos/* | Refactored functions to support excluded locations through feed options and updated internal helper methods. |
</details>



<details>
<summary>Comments suppressed due to low confidence (2)</summary>

**sdk/cosmos/azure-cosmos/tests/_fault_injection_transport_async.py:50**
* It appears the lambda incorrectly passes f['predicate'] instead of the request object. Update the lambda to use 'request' as the argument (e.g. lambda f: f['predicate'](request)).
```
matching_request_transformations = filter(lambda f: f["predicate"](f["predicate"]), iter(self.requestTransformations))
```
**sdk/cosmos/azure-cosmos/tests/_fault_injection_transport.py:40**
* The lambda for applying request transformations mistakenly passes f['predicate'] instead of the current request. It should be changed to pass 'request' (i.e. lambda f: f['predicate'](request)).
```
matching_request_transformations = filter(lambda f: f["predicate"](f["predicate"]), iter(self.requestTransformations))
```
</details>


state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-07T22:42:40Z
user: login: tvaron3
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-08T02:42:53Z
user: login: allenkim0129
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-08T17:18:04Z
user: login: allenkim0129
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-08T17:29:39Z
user: login: allenkim0129
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-08T17:30:01Z
user: login: tvaron3
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-08T21:41:17Z
user: login: allenkim0129
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-14T21:08:33Z
user: login: allenkim0129
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-14T21:09:19Z
user: login: kushagraThapar
body: Thank you @allenkim0129 for the changes, they look good from overall point of view, however, I have couple of questions on the public surface APIs, let's discuss them before merging this in. 
state: CHANGES_REQUESTED
author_association: MEMBER
submitted_at: 2025-05-15T04:50:14Z
user: login: allenkim0129
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-15T18:12:41Z
user: login: kushagraThapar
state: APPROVED
author_association: MEMBER
submitted_at: 2025-05-16T18:55:31Z
user: login: simorenoh
body: Just a couple of comments, thanks Allen!
state: CHANGES_REQUESTED
author_association: MEMBER
submitted_at: 2025-05-21T01:59:27Z
user: login: allenkim0129
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-21T17:00:02Z
user: login: allenkim0129
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-21T17:05:54Z
user: login: simorenoh
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-22T17:45:55Z
user: login: allenkim0129
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-22T19:22:44Z
user: login: allenkim0129
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-22T19:23:20Z
user: login: simorenoh
state: APPROVED
author_association: MEMBER
submitted_at: 2025-05-22T20:04:13Z
user: login: simorenoh
state: APPROVED
author_association: MEMBER
submitted_at: 2025-05-27T16:45:37Z

Review comments:
diff_hunk: @@ -3,6 +3,7 @@
 ### 4.11.0b2 (Unreleased)
 
 #### Features Added
+* Added ability to use request level `excluded_locations` on metadat calls, such as getting container properties. See [PR 40905](https://github.com/Azure/azure-sdk-for-python/pull/40905)
path: sdk/cosmos/azure-cosmos/CHANGELOG.md
user: login: tvaron3
body: Nit
```suggestion
* Added ability to use request level `excluded_locations` on metadata calls, such as getting container properties. See [PR 40905](https://github.com/Azure/azure-sdk-for-python/pull/40905)
```
created_at: 2025-05-08T01:24:00Z
author_association: MEMBER
diff_hunk: @@ -142,6 +154,11 @@ def predicate_is_document_operation(r: HttpRequest) -> bool:
 
         return is_document_operation
 
+    @staticmethod
+    def predicate_is_collection_operation(r: HttpRequest) -> bool:
path: sdk/cosmos/azure-cosmos/tests/_fault_injection_transport_async.py
user: login: tvaron3
body: I think we could expand this to be predicate_is_resource_type and allow us to have predicate for any resource types. 
created_at: 2025-05-08T01:26:16Z
author_association: MEMBER
diff_hunk: @@ -480,3 +480,17 @@ def IsCollectionChild(resourceType: str) -> bool:
             ResourceType.StoredProcedure,
             ResourceType.PartitionKey,
         )
+
+    @staticmethod
+    def IsDocumentChild(resourceType: str) -> bool:
+        return resourceType in (
+            ResourceType.Document,
+        )
+
+    @staticmethod
+    def IsMetadataChild(resourceType: str) -> bool:
+        return resourceType in (
path: sdk/cosmos/azure-cosmos/azure/cosmos/http_constants.py
user: login: tvaron3
body: Shouldn't we also be handling query plan calls or are will this be a follow up?  This was Abhijeet's pr for query plan calls. https://github.com/Azure/azure-sdk-for-java/pull/45196. Is PartitionKey a metadata call?
created_at: 2025-05-08T01:30:27Z
author_association: MEMBER
diff_hunk: @@ -169,6 +181,10 @@ def read(  # pylint:disable=docstring-missing-param
         :keyword dict[str, str] initial_headers: Initial headers to be sent as part of the request.
         :keyword response_hook: A callable invoked with the response metadata.
         :paramtype response_hook: Callable[[Mapping[str, str], Dict[str, Any]], None]
+        :keyword list[str] excluded_locations: Excluded locations to be skipped from preferred locations. The locations
path: sdk/cosmos/azure-cosmos/azure/cosmos/container.py
user: login: tvaron3
body: Should read container operations outside of the lifecycle of a document have excluded locations as well?
created_at: 2025-05-08T02:38:35Z
author_association: MEMBER
diff_hunk: @@ -169,6 +181,10 @@ def read(  # pylint:disable=docstring-missing-param
         :keyword dict[str, str] initial_headers: Initial headers to be sent as part of the request.
         :keyword response_hook: A callable invoked with the response metadata.
         :paramtype response_hook: Callable[[Mapping[str, str], Dict[str, Any]], None]
+        :keyword list[str] excluded_locations: Excluded locations to be skipped from preferred locations. The locations
path: sdk/cosmos/azure-cosmos/azure/cosmos/container.py
user: login: allenkim0129
body: `read()` API was called during `get_properties()` method. This was the main change for this PR
created_at: 2025-05-08T17:18:03Z
author_association: CONTRIBUTOR
diff_hunk: @@ -3,6 +3,7 @@
 ### 4.11.0b2 (Unreleased)
 
 #### Features Added
+* Added ability to use request level `excluded_locations` on metadat calls, such as getting container properties. See [PR 40905](https://github.com/Azure/azure-sdk-for-python/pull/40905)
path: sdk/cosmos/azure-cosmos/CHANGELOG.md
user: login: allenkim0129
body: Done
created_at: 2025-05-08T17:29:39Z
author_association: CONTRIBUTOR
diff_hunk: @@ -142,6 +154,11 @@ def predicate_is_document_operation(r: HttpRequest) -> bool:
 
         return is_document_operation
 
+    @staticmethod
+    def predicate_is_collection_operation(r: HttpRequest) -> bool:
path: sdk/cosmos/azure-cosmos/tests/_fault_injection_transport_async.py
user: login: allenkim0129
body: Sounds good. I updated it!
created_at: 2025-05-08T17:30:01Z
author_association: CONTRIBUTOR
diff_hunk: @@ -169,6 +181,10 @@ def read(  # pylint:disable=docstring-missing-param
         :keyword dict[str, str] initial_headers: Initial headers to be sent as part of the request.
         :keyword response_hook: A callable invoked with the response metadata.
         :paramtype response_hook: Callable[[Mapping[str, str], Dict[str, Any]], None]
+        :keyword list[str] excluded_locations: Excluded locations to be skipped from preferred locations. The locations
path: sdk/cosmos/azure-cosmos/azure/cosmos/container.py
user: login: tvaron3
body: Yes was just wondering if we want it when a customer calls the read call or only in the lifecycle of a document operation. In java it is only done in the lifecycle of a document operation. 
created_at: 2025-05-08T21:41:17Z
author_association: MEMBER
diff_hunk: @@ -169,6 +181,10 @@ def read(  # pylint:disable=docstring-missing-param
         :keyword dict[str, str] initial_headers: Initial headers to be sent as part of the request.
         :keyword response_hook: A callable invoked with the response metadata.
         :paramtype response_hook: Callable[[Mapping[str, str], Dict[str, Any]], None]
+        :keyword list[str] excluded_locations: Excluded locations to be skipped from preferred locations. The locations
path: sdk/cosmos/azure-cosmos/azure/cosmos/container.py
user: login: allenkim0129
body: Makes sense. I removed the docstring here.
created_at: 2025-05-14T21:08:32Z
author_association: CONTRIBUTOR
diff_hunk: @@ -480,3 +480,17 @@ def IsCollectionChild(resourceType: str) -> bool:
             ResourceType.StoredProcedure,
             ResourceType.PartitionKey,
         )
+
+    @staticmethod
+    def IsDocumentChild(resourceType: str) -> bool:
+        return resourceType in (
+            ResourceType.Document,
+        )
+
+    @staticmethod
+    def IsMetadataChild(resourceType: str) -> bool:
+        return resourceType in (
path: sdk/cosmos/azure-cosmos/azure/cosmos/http_constants.py
user: login: allenkim0129
body: Thank you for checking that! I updated it to handle query plan calls too.
created_at: 2025-05-14T21:09:18Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1142,8 +1146,8 @@ def QueryItemsChangeFeed(
             partition_key_range_id = options["partitionKeyRangeId"]
 
         return self._QueryChangeFeed(
-            collection_link, "Documents", options, partition_key_range_id, response_hook=response_hook, **kwargs
-        )
+            collection_link, "Documents", options, partition_key_range_id, response_hook=response_hook,
path: sdk/cosmos/azure-cosmos/azure/cosmos/_cosmos_client_connection.py
user: login: kushagraThapar
body: is this correct, or should this be "docs"?
created_at: 2025-05-15T04:22:55Z
author_association: MEMBER
diff_hunk: @@ -369,10 +381,10 @@ def mark_endpoint_unavailable(self, unavailable_endpoint: str, unavailable_opera
         )
         if not unavailability_info:
             self.location_unavailability_info_by_endpoint[unavailable_endpoint] = {
-                "operationType": set([unavailable_operation_type])
+                "operationType": {unavailable_operation_type}
path: sdk/cosmos/azure-cosmos/azure/cosmos/_location_cache.py
user: login: kushagraThapar
body: why are we changing this?
created_at: 2025-05-15T04:26:28Z
author_association: MEMBER
diff_hunk: @@ -369,10 +381,10 @@ def mark_endpoint_unavailable(self, unavailable_endpoint: str, unavailable_opera
         )
         if not unavailability_info:
             self.location_unavailability_info_by_endpoint[unavailable_endpoint] = {
-                "operationType": set([unavailable_operation_type])
+                "operationType": {unavailable_operation_type}
             }
         else:
-            unavailable_operations = set([unavailable_operation_type]).union(unavailability_info["operationType"])
+            unavailable_operations = {unavailable_operation_type}.union(unavailability_info["operationType"])
path: sdk/cosmos/azure-cosmos/azure/cosmos/_location_cache.py
user: login: kushagraThapar
body: this as well?
created_at: 2025-05-15T04:26:33Z
author_association: MEMBER
diff_hunk: @@ -62,6 +62,19 @@
 
 PartitionKeyType = Union[str, int, float, bool, Sequence[Union[str, int, float, bool, None]], Type[NonePartitionKeyValue]]  # pylint: disable=line-too-long
 
+def get_partition_key_from_properties(container_properties: Dict[str, Any]) -> PartitionKey:
path: sdk/cosmos/azure-cosmos/azure/cosmos/container.py
user: login: kushagraThapar
body: are these 3 public APIs? why do we need these 3 APIs?
created_at: 2025-05-15T04:47:53Z
author_association: MEMBER
diff_hunk: @@ -1142,8 +1146,8 @@ def QueryItemsChangeFeed(
             partition_key_range_id = options["partitionKeyRangeId"]
 
         return self._QueryChangeFeed(
-            collection_link, "Documents", options, partition_key_range_id, response_hook=response_hook, **kwargs
-        )
+            collection_link, "Documents", options, partition_key_range_id, response_hook=response_hook,
path: sdk/cosmos/azure-cosmos/azure/cosmos/_cosmos_client_connection.py
user: login: allenkim0129
body:  Good call. This was a legacy code from 2022, and I believe this is no longer needed. I made updates to match the workflow with `QueryItems()`.
created_at: 2025-05-15T18:07:20Z
author_association: CONTRIBUTOR
diff_hunk: @@ -369,10 +381,10 @@ def mark_endpoint_unavailable(self, unavailable_endpoint: str, unavailable_opera
         )
         if not unavailability_info:
             self.location_unavailability_info_by_endpoint[unavailable_endpoint] = {
-                "operationType": set([unavailable_operation_type])
+                "operationType": {unavailable_operation_type}
path: sdk/cosmos/azure-cosmos/azure/cosmos/_location_cache.py
user: login: allenkim0129
body: It was a nit pick which was a suggestion from Pycharm IDE to change to `literal {}`, but this actually caused pylint error, so I reverted this change. Thanks!
created_at: 2025-05-15T18:11:42Z
author_association: CONTRIBUTOR
diff_hunk: @@ -369,10 +381,10 @@ def mark_endpoint_unavailable(self, unavailable_endpoint: str, unavailable_opera
         )
         if not unavailability_info:
             self.location_unavailability_info_by_endpoint[unavailable_endpoint] = {
-                "operationType": set([unavailable_operation_type])
+                "operationType": {unavailable_operation_type}
             }
         else:
-            unavailable_operations = set([unavailable_operation_type]).union(unavailability_info["operationType"])
+            unavailable_operations = {unavailable_operation_type}.union(unavailability_info["operationType"])
path: sdk/cosmos/azure-cosmos/azure/cosmos/_location_cache.py
user: login: allenkim0129
body: It was a nit pick which was a suggestion from Pycharm IDE to change to `literal {}`, but this actually caused pylint error, so I reverted this change. Thanks!
created_at: 2025-05-15T18:11:45Z
author_association: CONTRIBUTOR
diff_hunk: @@ -62,6 +62,19 @@
 
 PartitionKeyType = Union[str, int, float, bool, Sequence[Union[str, int, float, bool, None]], Type[NonePartitionKeyValue]]  # pylint: disable=line-too-long
 
+def get_partition_key_from_properties(container_properties: Dict[str, Any]) -> PartitionKey:
path: sdk/cosmos/azure-cosmos/azure/cosmos/container.py
user: login: allenkim0129
body: These are not the public APIs, instead they are just static helper functions. I put them here since it doesn't need any member variables in `ContainerProxy` class
created_at: 2025-05-15T18:12:04Z
author_association: CONTRIBUTOR
diff_hunk: @@ -480,3 +480,17 @@ def IsCollectionChild(resourceType: str) -> bool:
             ResourceType.StoredProcedure,
             ResourceType.PartitionKey,
         )
+
+    @staticmethod
+    def IsDocumentChild(resourceType: str) -> bool:
+        return resourceType in (
+            ResourceType.Document,
+        )
path: sdk/cosmos/azure-cosmos/azure/cosmos/http_constants.py
user: login: simorenoh
body: is this code needed at all if the comparisons are against one single resource?
created_at: 2025-05-20T20:32:57Z
author_association: MEMBER
diff_hunk: @@ -480,3 +480,17 @@ def IsCollectionChild(resourceType: str) -> bool:
             ResourceType.StoredProcedure,
             ResourceType.PartitionKey,
         )
+
+    @staticmethod
+    def IsDocumentChild(resourceType: str) -> bool:
+        return resourceType in (
+            ResourceType.Document,
+        )
+
+    @staticmethod
+    def IsMetadataChild(resourceType: str) -> bool:
+        return resourceType in (
path: sdk/cosmos/azure-cosmos/azure/cosmos/http_constants.py
user: login: simorenoh
body: like Tomas pointed out, the PartitionKey resource is used for the DeleteAllItemsByPartitionKey method only - would that be considered a metadata call? Also, we have a check in the SDK for master resources - we should align this to go along with that as well (eg. rename to is_metadata_resource, move method to base.py). not sure if it makes sense to have these checks across different files
![image](https://github.com/user-attachments/assets/cc7732e5-2bf6-4e50-b82a-2aea90e83d07)

created_at: 2025-05-20T20:35:36Z
author_association: MEMBER
diff_hunk: @@ -681,9 +695,9 @@ def query_items(  # pylint:disable=docstring-missing-param
             feed_options["populateIndexMetrics"] = populate_index_metrics
         if partition_key is not None:
             partition_key_value = self._set_partition_key(partition_key)
-            if self.__is_prefix_partitionkey(partition_key):
+            properties = self._get_properties_with_feed_options(feed_options)
+            if is_prefix_partition_key(properties, partition_key):
                 kwargs["isPrefixPartitionQuery"] = True
-                properties = self._get_properties()
path: sdk/cosmos/azure-cosmos/azure/cosmos/container.py
user: login: simorenoh
body: nitpick: why did we move this logic outside of the if statement if it is only needed inside of it?
created_at: 2025-05-21T01:49:35Z
author_association: MEMBER
diff_hunk: @@ -681,9 +695,9 @@ def query_items(  # pylint:disable=docstring-missing-param
             feed_options["populateIndexMetrics"] = populate_index_metrics
         if partition_key is not None:
             partition_key_value = self._set_partition_key(partition_key)
-            if self.__is_prefix_partitionkey(partition_key):
+            properties = self._get_properties_with_feed_options(feed_options)
+            if is_prefix_partition_key(properties, partition_key):
                 kwargs["isPrefixPartitionQuery"] = True
-                properties = self._get_properties()
path: sdk/cosmos/azure-cosmos/azure/cosmos/container.py
user: login: allenkim0129
body: Since it is used in 2 places: `if-statement` and `partitionKeyDefinition`, this was an optimization.

Previously `_get_properties` was called 2 times in `self._get_properties_with_feed_options` and inside of the if block.

I wanted to reduce number of duplicate calls.
created_at: 2025-05-21T17:00:02Z
author_association: CONTRIBUTOR
diff_hunk: @@ -480,3 +480,17 @@ def IsCollectionChild(resourceType: str) -> bool:
             ResourceType.StoredProcedure,
             ResourceType.PartitionKey,
         )
+
+    @staticmethod
+    def IsDocumentChild(resourceType: str) -> bool:
+        return resourceType in (
+            ResourceType.Document,
+        )
path: sdk/cosmos/azure-cosmos/azure/cosmos/http_constants.py
user: login: allenkim0129
body: We could do that, but this was added to avoid any code duplication and to easily maintain/update this kind of checks. If we ever need to update the condition, we can update a single point instead of all instances.
created_at: 2025-05-21T17:05:54Z
author_association: CONTRIBUTOR
diff_hunk: @@ -480,3 +480,17 @@ def IsCollectionChild(resourceType: str) -> bool:
             ResourceType.StoredProcedure,
             ResourceType.PartitionKey,
         )
+
+    @staticmethod
+    def IsDocumentChild(resourceType: str) -> bool:
+        return resourceType in (
+            ResourceType.Document,
+        )
path: sdk/cosmos/azure-cosmos/azure/cosmos/http_constants.py
user: login: simorenoh
body: that's fair, but if this was ever to change from just document child, the method name would need to change as well, and as such the code changes everywhere too - basically, a document child will only ever be a document
created_at: 2025-05-22T17:45:55Z
author_association: MEMBER
diff_hunk: @@ -480,3 +480,17 @@ def IsCollectionChild(resourceType: str) -> bool:
             ResourceType.StoredProcedure,
             ResourceType.PartitionKey,
         )
+
+    @staticmethod
+    def IsDocumentChild(resourceType: str) -> bool:
+        return resourceType in (
+            ResourceType.Document,
+        )
+
+    @staticmethod
+    def IsMetadataChild(resourceType: str) -> bool:
+        return resourceType in (
path: sdk/cosmos/azure-cosmos/azure/cosmos/http_constants.py
user: login: allenkim0129
body: As we discussed offline, `PartitionKey` was removed from meta data check. Also, instead of using `IsMetadataChild`, I updated to use the existing method `IsMasterResource` methods to check the metadata calls. 
created_at: 2025-05-22T19:22:44Z
author_association: CONTRIBUTOR
diff_hunk: @@ -480,3 +480,17 @@ def IsCollectionChild(resourceType: str) -> bool:
             ResourceType.StoredProcedure,
             ResourceType.PartitionKey,
         )
+
+    @staticmethod
+    def IsDocumentChild(resourceType: str) -> bool:
+        return resourceType in (
+            ResourceType.Document,
+        )
path: sdk/cosmos/azure-cosmos/azure/cosmos/http_constants.py
user: login: allenkim0129
body: Sounds good. I removed this method
created_at: 2025-05-22T19:23:20Z
author_association: CONTRIBUTOR

PR comments:
user: login: allenkim0129
created_at: 2025-05-06T00:54:12Z
body: /azp run python - cosmos - tests
author_association: CONTRIBUTOR
user: login: azure-pipelines[bot]
created_at: 2025-05-06T00:54:29Z
body: <samp>
Azure Pipelines successfully started running 1 pipeline(s).<br>

</samp>
author_association: NONE
user: login: allenkim0129
created_at: 2025-05-06T18:10:41Z
body: /azp run python - cosmos - tests
author_association: CONTRIBUTOR
user: login: azure-pipelines[bot]
created_at: 2025-05-06T18:11:01Z
body: <samp>
Azure Pipelines successfully started running 1 pipeline(s).<br>

</samp>
author_association: NONE
user: login: allenkim0129
created_at: 2025-05-06T19:45:43Z
body: /azp run python - cosmos - tests
author_association: CONTRIBUTOR
user: login: azure-pipelines[bot]
created_at: 2025-05-06T19:46:03Z
body: <samp>
Azure Pipelines successfully started running 1 pipeline(s).<br>

</samp>
author_association: NONE
user: login: allenkim0129
created_at: 2025-05-06T21:38:31Z
body: /azp run python - cosmos - tests
author_association: CONTRIBUTOR
user: login: azure-pipelines[bot]
created_at: 2025-05-06T21:38:50Z
body: <samp>
Azure Pipelines successfully started running 1 pipeline(s).<br>

</samp>
author_association: NONE
user: login: azure-sdk
created_at: 2025-05-06T21:57:56Z
body: ## API Change Check

APIView identified API level changes in this PR and created the following API reviews

[azure-cosmos](https://spa.apiview.dev/review/c1c8a8095ae24413aa1bb1705a7c7b77?activeApiRevisionId=8caa2bd027ba41cea89cc36d4288c3d2)
<!-- Fetch URI: https://apiview.dev/api/pullrequests?pullRequestNumber=40905&repoName=Azure/azure-sdk-for-python&commitSHA=51c8bef679bf575790160b6345c08b2eb8803a51 -->
author_association: COLLABORATOR
user: login: azure-sdk
created_at: 2025-05-07T09:37:24Z
body: **API change check**

APIView has identified API level changes in this PR and created following API reviews.

[azure-cosmos](https://apiview.dev/Assemblies/Review/c1c8a8095ae24413aa1bb1705a7c7b77?revisionId=8caa2bd027ba41cea89cc36d4288c3d2)


author_association: COLLABORATOR
user: login: allenkim0129
created_at: 2025-05-07T22:42:11Z
body: /azp run python - cosmos - tests
author_association: CONTRIBUTOR
user: login: azure-pipelines[bot]
created_at: 2025-05-07T22:42:28Z
body: <samp>
Azure Pipelines successfully started running 1 pipeline(s).<br>

</samp>
author_association: NONE
user: login: allenkim0129
created_at: 2025-05-07T22:47:31Z
body: /azp run python - cosmos - tests
author_association: CONTRIBUTOR
user: login: azure-pipelines[bot]
created_at: 2025-05-07T22:47:47Z
body: <samp>
Azure Pipelines successfully started running 1 pipeline(s).<br>

</samp>
author_association: NONE
user: login: allenkim0129
created_at: 2025-05-14T21:09:31Z
body: /azp run python - cosmos - tests
author_association: CONTRIBUTOR
user: login: azure-pipelines[bot]
created_at: 2025-05-14T21:09:52Z
body: <samp>
Azure Pipelines successfully started running 1 pipeline(s).<br>

</samp>
author_association: NONE
user: login: allenkim0129
created_at: 2025-05-15T18:17:11Z
body: /azp run python - cosmos - tests

author_association: CONTRIBUTOR
user: login: azure-pipelines[bot]
created_at: 2025-05-15T18:17:29Z
body: <samp>
Azure Pipelines successfully started running 1 pipeline(s).<br>

</samp>
author_association: NONE
user: login: allenkim0129
created_at: 2025-05-15T18:46:18Z
body: /azp run python - cosmos - tests
author_association: CONTRIBUTOR
user: login: azure-pipelines[bot]
created_at: 2025-05-15T18:46:38Z
body: <samp>
Azure Pipelines successfully started running 1 pipeline(s).<br>

</samp>
author_association: NONE
user: login: allenkim0129
created_at: 2025-05-16T18:53:28Z
body: /azp run python - cosmos - tests
author_association: CONTRIBUTOR
user: login: azure-pipelines[bot]
created_at: 2025-05-16T18:53:46Z
body: <samp>
Azure Pipelines successfully started running 1 pipeline(s).<br>

</samp>
author_association: NONE