Description
Package Name: azure-storage-blob
and likely others using the same code generator.
Package Version: 12.25.1
(and likely others)
Operating System: Ubuntu 22.04 (but the issue is OS-agnostic)
Python Version: 3.10
Describe the bug
There is a systemic bug in the generated asynchronous operation methods, likely originating from the AutoRest Python templates.
When an async method like ContainerClient.list_blobs(max_results=...)
is called, the service-level parameter (max_results
) is correctly used to build the HTTP request URL. However, the parameter is not removed from the **kwargs
dictionary that is subsequently passed down through the azure-core
pipeline.
This causes the service-level parameter to be passed all the way to the underlying aiohttp
transport layer, which does not recognize it, resulting in a TypeError: ClientSession._request() got an unexpected keyword argument 'max_results'
.
The core issue is that the generated async
wrapper method fails to clean up its kwargs
after consuming the named parameters.
To Reproduce
Steps to reproduce the behavior:
- Set up a clean, isolated Python environment:
python3 -m venv venv source venv/bin/activate
- Create a
requirements.txt
file with the following content: This specifies the latest stable versions that reliably demonstrate the issue.azure-storage-blob==12.25.1 aiohttp==3.8.6
- Install the requirements:
pip install -r requirements.txt
- Create a minimal test script (
test_bug.py
):
(Note: This script requires aconfig.json
file in the same directory containing a validSTORAGE_CONN_STR
andBLOB_CONTAINER_NAME
)import asyncio import json from azure.storage.blob.aio import ContainerClient async def main(): print("--- Minimal Reproducer ---") try: with open("config.json") as f: config = json.load(f) storage_conn_str = config["STORAGE_CONN_STR"] container_name = config["BLOB_CONTAINER_NAME"] print(f"✔ Config loaded for container: {container_name}") except Exception as e: print(f"✖ Failed to load config: {e}") return client = ContainerClient.from_connection_string( storage_conn_str, container_name ) async with client: try: # This call fails due to the bug print("Attempting to call client.list_blobs(max_results=1)...") async for _ in client.list_blobs(max_results=1): pass print("SUCCESS: list_blobs(max_results=1) worked.") except TypeError as e: import traceback print(f"\nFAIL: Bug reproduced.\n") traceback.print_exc() if __name__ == "__main__": asyncio.run(main())
- Run the script:
The script will fail with the
python3 test_bug.py
TypeError
.
Expected behavior
The script should execute successfully without errors. The max_results
parameter should be used to construct the URL query string and then be consumed, not passed down to the transport layer. The expected output is:
--- Minimal Reproducer ---
✔ Config loaded for container: ...
Attempting to call client.list_blobs(max_results=1)...
SUCCESS: list_blobs(max_results=1) worked.
Screenshots
The traceback from the failure is the most relevant "screenshot":
FAIL: Bug reproduced.
Traceback (most recent call last):
File "/path/to/test_bug.py", line 29, in main
async for _ in client.list_blobs(max_results=1):
File "/path/to/venv/lib/python3.10/site-packages/azure/core/async_paging.py", line 142, in __anext__
return await self.__anext__()
...
File "/path/to/venv/lib/python3.10/site-packages/azure/core/pipeline/transport/_aiohttp.py", line 317, in send
result = await self.session.request( # type: ignore
File "/path/to/venv/lib/python3.10/site-packages/aiohttp/client.py", line 380, in request
return _RequestContextManager(self._request(method, url, **kwargs))
TypeError: ClientSession._request() got an unexpected keyword argument 'max_results'
Additional context
This issue was diagnosed through a long and detailed process that ruled out environmental factors (venv corruption, caching, network issues, etc.). The final evidence points to a code-generation issue in the AutoRest templates for Python.
Workaround: The bug is not present in all convenience methods. We found that using container_client.walk_blobs(results_per_page=1)
works correctly, suggesting it uses a different, non-buggy code path for argument handling.
Suggested Fix: The code-generation template for async
operation wrappers should be modified to pop
the service-level named parameters from the kwargs
dictionary before calling _pipeline.run()
.
Example diff
for the faulty method in azure/storage/blob/_generated/aio/operations/_container_operations.py
:
--- a/azure/storage/blob/_generated/aio/operations/_container_operations.py
+++ b/azure/storage/blob/_generated/aio/operations/_container_operations.py
@@ -1651,6 +1651,15 @@
_request.url = self._client.format_url(_request.url)
_stream = False
+
+ # Pop the service-level parameters from kwargs to prevent them from being passed down
+ # to the transport layer, which would cause a TypeError.
+ kwargs.pop("prefix", None)
+ kwargs.pop("marker", None)
+ kwargs.pop("maxresults", None)
+ kwargs.pop("include", None)
+ kwargs.pop("timeout", None)
+ kwargs.pop("request_id_parameter", None)
+
pipeline_response: PipelineResponse = await self._client._pipeline.run( # pylint: disable=protected-access
_request, stream=_stream, **kwargs
)
This bug was investigated and diagnosed in partnership with my AI assistant.