Skip to content

Commit 8f58e9c

Browse files
Fix TypeError when parameters=None in query_items (#43681)
* Fix TypeError when parameters=None in query_items Fixes #43662 Ensure parameters=None is treated the same as omitting parameters, preventing TypeError in HTTP request layer. * Update sdk/cosmos/azure-cosmos/azure/cosmos/container.py Co-authored-by: Copilot <[email protected]> * Update sdk/cosmos/azure-cosmos/azure/cosmos/aio/_container.py Co-authored-by: Copilot <[email protected]> * Added necessary tests coverage * Minor changes to tests as per the request --------- Co-authored-by: Copilot <[email protected]>
1 parent 8e49d4a commit 8f58e9c

File tree

4 files changed

+205
-6
lines changed

4 files changed

+205
-6
lines changed

sdk/cosmos/azure-cosmos/azure/cosmos/aio/_container.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -821,10 +821,12 @@ def query_items(
821821
feed_options["correlatedActivityId"] = GenerateGuidId()
822822

823823
# Set query with 'query' and 'parameters' from kwargs
824-
if utils.valid_key_value_exist(kwargs, "parameters"):
825-
query = {"query": kwargs.pop("query", None), "parameters": kwargs.pop("parameters", None)}
824+
query_str = kwargs.pop("query", None)
825+
parameters = kwargs.pop("parameters", None)
826+
if parameters is not None:
827+
query = {"query": query_str, "parameters": parameters}
826828
else:
827-
query = kwargs.pop("query", None)
829+
query = query_str
828830

829831
# Set method to get/cache container properties
830832
kwargs["containerProperties"] = self._get_properties_with_options

sdk/cosmos/azure-cosmos/azure/cosmos/container.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -933,10 +933,12 @@ def query_items( # pylint:disable=docstring-missing-param
933933
feed_options["containerRID"] = self.__get_client_container_caches()[self.container_link]["_rid"]
934934

935935
# Set query with 'query' and 'parameters' from kwargs
936-
if utils.valid_key_value_exist(kwargs, "parameters"):
937-
query = {"query": kwargs.pop("query", None), "parameters": kwargs.pop("parameters", None)}
936+
query_str = kwargs.pop("query", None)
937+
parameters = kwargs.pop("parameters", None)
938+
if parameters is not None:
939+
query = {"query": query_str, "parameters": parameters}
938940
else:
939-
query = kwargs.pop("query", None)
941+
query = query_str
940942

941943
# Set range filters for a query. Options are either 'feed_range' or 'partition_key'
942944
utils.verify_exclusive_arguments(["feed_range", "partition_key"], **kwargs)

sdk/cosmos/azure-cosmos/tests/test_query.py

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -766,6 +766,105 @@ def _MockNextFunction(self):
766766
else:
767767
raise StopIteration
768768

769+
def test_query_items_with_parameters_none(self):
770+
"""Test that query_items handles parameters=None correctly (issue #43662)."""
771+
created_collection = self.created_db.create_container(
772+
"test_params_none_" + str(uuid.uuid4()), PartitionKey(path="/pk"))
773+
774+
# Create test documents
775+
doc1_id = 'doc1_' + str(uuid.uuid4())
776+
doc2_id = 'doc2_' + str(uuid.uuid4())
777+
created_collection.create_item(body={'pk': 'pk1', 'id': doc1_id, 'value1': 1})
778+
created_collection.create_item(body={'pk': 'pk2', 'id': doc2_id, 'value1': 2})
779+
780+
# Test 1: Explicitly passing parameters=None should not cause TypeError
781+
query = 'SELECT * FROM c'
782+
query_iterable = created_collection.query_items(
783+
query=query,
784+
parameters=None,
785+
enable_cross_partition_query=True
786+
)
787+
results = list(query_iterable)
788+
self.assertEqual(len(results), 2)
789+
790+
# Test 2: parameters=None with partition_key should work
791+
query_iterable = created_collection.query_items(
792+
query=query,
793+
parameters=None,
794+
partition_key='pk1'
795+
)
796+
results = list(query_iterable)
797+
self.assertEqual(len(results), 1)
798+
self.assertEqual(results[0]['id'], doc1_id)
799+
800+
# Test 3: Verify parameterized query still works with actual parameters
801+
query_with_params = 'SELECT * FROM c WHERE c.value1 = @value'
802+
query_iterable = created_collection.query_items(
803+
query=query_with_params,
804+
parameters=[{'name': '@value', 'value': 2}],
805+
enable_cross_partition_query=True
806+
)
807+
results = list(query_iterable)
808+
self.assertEqual(len(results), 1)
809+
self.assertEqual(results[0]['id'], doc2_id)
810+
811+
# Test 4: Query without parameters argument should work (default behavior)
812+
query_iterable = created_collection.query_items(
813+
query=query,
814+
enable_cross_partition_query=True
815+
)
816+
results = list(query_iterable)
817+
self.assertEqual(len(results), 2)
818+
819+
self.created_db.delete_container(created_collection.id)
820+
821+
def test_query_items_parameters_none_with_options(self):
822+
"""Test parameters=None works with various query options."""
823+
created_collection = self.created_db.create_container(
824+
"test_params_none_opts_" + str(uuid.uuid4()), PartitionKey(path="/pk"))
825+
826+
# Create multiple test documents
827+
for i in range(5):
828+
doc_id = f'doc_{i}_' + str(uuid.uuid4())
829+
created_collection.create_item(body={'pk': 'test', 'id': doc_id, 'index': i})
830+
831+
# Test with parameters=None and max_item_count
832+
query = 'SELECT * FROM c ORDER BY c.index'
833+
query_iterable = created_collection.query_items(
834+
query=query,
835+
parameters=None,
836+
partition_key='test',
837+
max_item_count=2
838+
)
839+
840+
# Verify pagination works
841+
page_count = 0
842+
total_items = 0
843+
for page in query_iterable.by_page():
844+
page_count += 1
845+
items = list(page)
846+
total_items += len(items)
847+
self.assertLessEqual(len(items), 2)
848+
849+
self.assertEqual(total_items, 5)
850+
self.assertGreaterEqual(page_count, 2) # Should have multiple pages
851+
852+
# Test with parameters=None and populate_query_metrics
853+
query_iterable = created_collection.query_items(
854+
query=query,
855+
parameters=None,
856+
partition_key='test',
857+
populate_query_metrics=True
858+
)
859+
results = list(query_iterable)
860+
self.assertEqual(len(results), 5)
861+
862+
# Verify query metrics were populated
863+
metrics_header_name = 'x-ms-documentdb-query-metrics'
864+
self.assertTrue(metrics_header_name in created_collection.client_connection.last_response_headers)
865+
866+
self.created_db.delete_container(created_collection.id)
867+
769868

770869
if __name__ == "__main__":
771870
unittest.main()

sdk/cosmos/azure-cosmos/tests/test_query_async.py

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -756,6 +756,102 @@ async def _MockExecuteFunctionTimeoutFailoverRetry(self, function, *args, **kwar
756756
raise ex_to_raise
757757
return await self.OriginalExecuteFunction(function, *args, **kwargs)
758758

759+
async def test_query_items_with_parameters_none_async(self):
760+
"""Test that query_items handles parameters=None correctly (issue #43662)."""
761+
created_collection = await self.created_db.create_container(
762+
"test_params_none_" + str(uuid.uuid4()), PartitionKey(path="/pk"))
763+
764+
# Create test documents
765+
doc1_id = 'doc1_' + str(uuid.uuid4())
766+
doc2_id = 'doc2_' + str(uuid.uuid4())
767+
await created_collection.create_item(body={'pk': 'pk1', 'id': doc1_id, 'value1': 1})
768+
await created_collection.create_item(body={'pk': 'pk2', 'id': doc2_id, 'value1': 2})
769+
770+
# Test 1: Explicitly passing parameters=None should not cause TypeError
771+
query = 'SELECT * FROM c'
772+
query_iterable = created_collection.query_items(
773+
query=query,
774+
parameters=None
775+
)
776+
results = [item async for item in query_iterable]
777+
assert len(results) == 2
778+
779+
# Test 2: parameters=None with partition_key should work
780+
query_iterable = created_collection.query_items(
781+
query=query,
782+
parameters=None,
783+
partition_key='pk1'
784+
)
785+
results = [item async for item in query_iterable]
786+
assert len(results) == 1
787+
assert results[0]['id'] == doc1_id
788+
789+
# Test 3: Verify parameterized query still works with actual parameters
790+
query_with_params = 'SELECT * FROM c WHERE c.value1 = @value'
791+
query_iterable = created_collection.query_items(
792+
query=query_with_params,
793+
parameters=[{'name': '@value', 'value': 2}]
794+
)
795+
results = [item async for item in query_iterable]
796+
assert len(results) == 1
797+
assert results[0]['id'] == doc2_id
798+
799+
# Test 4: Query without parameters argument should work (default behavior)
800+
query_iterable = created_collection.query_items(
801+
query=query
802+
)
803+
results = [item async for item in query_iterable]
804+
assert len(results) == 2
805+
806+
await self.created_db.delete_container(created_collection.id)
807+
808+
async def test_query_items_parameters_none_with_options_async(self):
809+
"""Test parameters=None works with various query options."""
810+
created_collection = await self.created_db.create_container(
811+
"test_params_none_opts_" + str(uuid.uuid4()), PartitionKey(path="/pk"))
812+
813+
# Create multiple test documents
814+
for i in range(5):
815+
doc_id = f'doc_{i}_' + str(uuid.uuid4())
816+
await created_collection.create_item(body={'pk': 'test', 'id': doc_id, 'index': i})
817+
818+
# Test with parameters=None and max_item_count
819+
query = 'SELECT * FROM c ORDER BY c.index'
820+
query_iterable = created_collection.query_items(
821+
query=query,
822+
parameters=None,
823+
partition_key='test',
824+
max_item_count=2
825+
)
826+
827+
# Verify pagination works
828+
page_count = 0
829+
total_items = 0
830+
async for page in query_iterable.by_page():
831+
page_count += 1
832+
items = [item async for item in page]
833+
total_items += len(items)
834+
assert len(items) <= 2
835+
836+
assert total_items == 5
837+
assert page_count >= 2 # Should have multiple pages
838+
839+
# Test with parameters=None and populate_query_metrics
840+
query_iterable = created_collection.query_items(
841+
query=query,
842+
parameters=None,
843+
partition_key='test',
844+
populate_query_metrics=True
845+
)
846+
results = [item async for item in query_iterable]
847+
assert len(results) == 5
848+
849+
# Verify query metrics were populated
850+
metrics_header_name = 'x-ms-documentdb-query-metrics'
851+
assert metrics_header_name in created_collection.client_connection.last_response_headers
852+
853+
await self.created_db.delete_container(created_collection.id)
854+
759855

760856
if __name__ == '__main__':
761857
unittest.main()

0 commit comments

Comments
 (0)