|
| 1 | +--- |
| 2 | +title: Understanding common response types in the Azure SDK for Python |
| 3 | +description: Learn types of objects you receive from SDK operations when using the Azure SDK for Python. |
| 4 | +ms.date: 7/10/2025 |
| 5 | +ms.topic: conceptual |
| 6 | +ms.custom: devx-track-python, py-fresh-zinc |
| 7 | +--- |
| 8 | + |
| 9 | +# Understanding common response types in the Azure SDK for Python |
| 10 | + |
| 11 | +The Azure SDK for Python abstracts calls to the underlying Azure service communication protocol, whether that protocol is HTTP or AMQP (which is used for messaging SDKs like ServiceBus, EventHubs, etc.). For example, if you use one of the libraries that utilizes HTTP, then the Azure SDK for Python is making HTTP requests and receiving HTTP responses under the hood. The SDK abstracts away this complexity, allowing you to work with intuitive Python objects instead of raw HTTP responses or JSON payloads. |
| 12 | + |
| 13 | +Understanding the types of objects you receive from SDK operations is essential for writing effective Azure applications. This article explains the common response types you encounter and how they relate to the underlying HTTP communication. |
| 14 | + |
| 15 | +> [!NOTE] |
| 16 | +> This article only examines the HTTP scenario, not the AMQP scenario. |
| 17 | +
|
| 18 | +## Deserialized Python objects |
| 19 | + |
| 20 | +The Azure SDK for Python prioritizes developer productivity by returning strongly typed Python objects from service operations. Instead of parsing JSON or handling HTTP status codes directly, you work with resource models that represent Azure resources as Python objects. |
| 21 | + |
| 22 | +For example, when you retrieve a blob from Azure Storage, you receive a BlobProperties object with attributes like name, size, and last_modified, rather than a raw JSON dictionary: |
| 23 | + |
| 24 | +```python |
| 25 | +from azure.storage.blob import BlobServiceClient |
| 26 | + |
| 27 | +# Connect to storage account |
| 28 | +blob_service_client = BlobServiceClient.from_connection_string(connection_string) |
| 29 | +container_client = blob_service_client.get_container_client("mycontainer") |
| 30 | + |
| 31 | +# Get blob properties - returns a BlobProperties object |
| 32 | +blob_client = container_client.get_blob_client("myblob.txt") |
| 33 | +properties = blob_client.get_blob_properties() |
| 34 | + |
| 35 | +# Access properties as Python attributes |
| 36 | +print(f"Blob name: {properties.name}") |
| 37 | +print(f"Blob size: {properties.size} bytes") |
| 38 | +print(f"Last modified: {properties.last_modified}") |
| 39 | +``` |
| 40 | + |
| 41 | +### Where the data comes from |
| 42 | + |
| 43 | +Understanding the data flow helps you appreciate what the SDK does behind the scenes: |
| 44 | + |
| 45 | +- Your code calls an SDK method - You invoke a method like get_blob_properties() |
| 46 | +- The SDK constructs an HTTP request - The SDK builds the appropriate HTTP request with headers, authentication, and query parameters |
| 47 | +- Azure service responds - The service returns an HTTP response, typically with a JSON payload in the response body |
| 48 | +- The SDK processes the response - The SDK: |
| 49 | + - Checks the HTTP status code |
| 50 | + - Parses the response body (usually JSON) |
| 51 | + - Validates the data against expected schemas |
| 52 | + - Maps the data to Python model objects |
| 53 | +- Your code receives Python objects - You work with the deserialized objects, not raw HTTP data |
| 54 | + |
| 55 | +This abstraction allows you to focus on your application logic rather than HTTP protocol details. |
| 56 | + |
| 57 | +## Common response types |
| 58 | + |
| 59 | +The Azure SDK for Python uses several standard response types across all services. Understanding these types helps you work effectively with any Azure service. |
| 60 | + |
| 61 | +### Resource models |
| 62 | + |
| 63 | +Most SDK operations return resource models—Python objects that represent Azure resources. These models are service-specific but follow consistent patterns: |
| 64 | + |
| 65 | +```python |
| 66 | +# Azure Key Vault example |
| 67 | +from azure.keyvault.secrets import SecretClient |
| 68 | + |
| 69 | +secret_client = SecretClient(vault_url=vault_url, credential=credential) |
| 70 | +secret = secret_client.get_secret("mysecret") # Returns KeyVaultSecret |
| 71 | + |
| 72 | +print(f"Secret name: {secret.name}") |
| 73 | +print(f"Secret value: {secret.value}") |
| 74 | +print(f"Secret version: {secret.properties.version}") |
| 75 | + |
| 76 | +# Azure Cosmos DB example |
| 77 | +from azure.cosmos import CosmosClient |
| 78 | + |
| 79 | +cosmos_client = CosmosClient(url=cosmos_url, credential=credential) |
| 80 | +database = cosmos_client.get_database_client("mydatabase") |
| 81 | +container = database.get_container_client("mycontainer") |
| 82 | +item = container.read_item(item="item-id", partition_key="partition-value") # Returns dict |
| 83 | + |
| 84 | +print(f"Item ID: {item['id']}") |
| 85 | +``` |
| 86 | + |
| 87 | +### ItemPaged for collection results |
| 88 | + |
| 89 | +When listing resources, the SDK returns `ItemPaged` objects that handle pagination transparently: |
| 90 | + |
| 91 | +```python |
| 92 | +from azure.storage.blob import BlobServiceClient |
| 93 | +from azure.core.paging import ItemPaged |
| 94 | + |
| 95 | +blob_service_client = BlobServiceClient.from_connection_string(connection_string) |
| 96 | +container_client = blob_service_client.get_container_client("mycontainer") |
| 97 | + |
| 98 | +# list_blobs returns ItemPaged[BlobProperties] |
| 99 | +blobs: ItemPaged[BlobProperties] = container_client.list_blobs() |
| 100 | + |
| 101 | +# Iterate naturally - SDK handles pagination |
| 102 | +for blob in blobs: |
| 103 | + print(f"Blob: {blob.name}, Size: {blob.size}") |
| 104 | +``` |
| 105 | + |
| 106 | +## Accessing the raw HTTP response |
| 107 | + |
| 108 | +While the SDK's high-level abstractions meet most needs, you sometimes need access to the underlying HTTP response. Common scenarios include: |
| 109 | + |
| 110 | +- Debugging failed requests |
| 111 | +- Accessing custom response headers |
| 112 | +- Implementing custom retry logic |
| 113 | +- Working with nonstandard response formats |
| 114 | + |
| 115 | +Most SDK methods accept a `raw_response_hook` parameter: |
| 116 | + |
| 117 | +```python |
| 118 | +from azure.keyvault.secrets import SecretClient |
| 119 | + |
| 120 | +secret_client = SecretClient(vault_url=vault_url, credential=credential) |
| 121 | + |
| 122 | +def inspect_response(response): |
| 123 | + # Access the raw HTTP response |
| 124 | + print(f"Request URL: {response.http_request.url}") |
| 125 | + print(f"Status code: {response.http_response.status_code}") |
| 126 | + print(f"Response headers: {dict(response.http_response.headers)}") |
| 127 | + |
| 128 | + # Access custom headers |
| 129 | + request_id = response.http_response.headers.get('x-ms-request-id') |
| 130 | + print(f"Request ID: {request_id}") |
| 131 | + |
| 132 | + # Must return the response |
| 133 | + return response |
| 134 | + |
| 135 | +# Hook is called before deserialization |
| 136 | +secret = secret_client.get_secret("mysecret", raw_response_hook=inspect_response) |
| 137 | +``` |
| 138 | + |
| 139 | +## Paging and iterators |
| 140 | + |
| 141 | +Azure services often return large collections of resources. The SDK uses ItemPaged to handle these collections efficiently without loading everything into memory at once. |
| 142 | + |
| 143 | +### Automatic pagination |
| 144 | + |
| 145 | +The SDK automatically fetches new pages as you iterate: |
| 146 | + |
| 147 | +```python |
| 148 | +# List all blobs - could be thousands |
| 149 | +blobs = container_client.list_blobs() |
| 150 | + |
| 151 | +# SDK fetches pages as needed during iteration |
| 152 | +for blob in blobs: |
| 153 | + process_blob(blob) # Pages loaded on-demand |
| 154 | +``` |
| 155 | + |
| 156 | +### Working with pages explicitly |
| 157 | + |
| 158 | +You can also work with pages directly when needed: |
| 159 | + |
| 160 | +```python |
| 161 | +blobs = container_client.list_blobs() |
| 162 | + |
| 163 | +# Process by page |
| 164 | +for page in blobs.by_page(): |
| 165 | + print(f"Processing page with {len(list(page))} items") |
| 166 | + for blob in page: |
| 167 | + process_blob(blob) |
| 168 | +``` |
| 169 | + |
| 170 | +### Controlling page size |
| 171 | + |
| 172 | +Many list operations accept a results_per_page parameter: |
| 173 | + |
| 174 | +```python |
| 175 | +# Fetch 100 items per page instead of the default |
| 176 | +blobs = container_client.list_blobs(results_per_page=100) |
| 177 | +``` |
| 178 | + |
| 179 | +Some methods for some Azure services have other mechanisms for controlling page size. For example, KeyVault and Azure Search use the `top` kwarg to limit results per call. See the [source code](https://github.com/Azure/azure-sdk-for-python/blob/0cf4523c054fc793c6ce46616daa5e23f9607d33/sdk/search/azure-search-documents/azure/search/documents/_search_client.py#L174) for Azure Search's `search()` method as an example. |
| 180 | + |
| 181 | + |
| 182 | +## Special case: Long-running operations and pollers |
| 183 | + |
| 184 | + |
| 185 | +Some Azure operations can't complete immediately. Examples include: |
| 186 | + |
| 187 | +- Creating or deleting virtual machines |
| 188 | +- Deploying ARM templates |
| 189 | +- Training machine learning models |
| 190 | +- Copying large blobs |
| 191 | + |
| 192 | +These operations return poller objects that track the operation's progress. |
| 193 | + |
| 194 | +### Working with pollers |
| 195 | + |
| 196 | +```python |
| 197 | +from azure.mgmt.storage import StorageManagementClient |
| 198 | + |
| 199 | +storage_client = StorageManagementClient(credential, subscription_id) |
| 200 | + |
| 201 | +# Start storage account creation |
| 202 | +poller = storage_client.storage_accounts.begin_create( |
| 203 | + resource_group_name="myresourcegroup", |
| 204 | + account_name="mystorageaccount", |
| 205 | + parameters=storage_parameters |
| 206 | +) |
| 207 | + |
| 208 | +# Option 1: Wait for completion (blocking) |
| 209 | +storage_account = poller.result() |
| 210 | + |
| 211 | +# Option 2: Check status periodically |
| 212 | +while not poller.done(): |
| 213 | + print(f"Status: {poller.status()}") |
| 214 | + time.sleep(5) |
| 215 | + |
| 216 | +storage_account = poller.result() |
| 217 | +``` |
| 218 | + |
| 219 | +### Asynchronous pollers |
| 220 | + |
| 221 | +When using async/await patterns, you work with `AsyncLROPoller`: |
| 222 | + |
| 223 | +```python |
| 224 | +from azure.storage.blob.aio import BlobServiceClient |
| 225 | + |
| 226 | +async with BlobServiceClient.from_connection_string(connection_string) as client: |
| 227 | + container_client = client.get_container_client("mycontainer") |
| 228 | + |
| 229 | + # Start async copy operation |
| 230 | + blob_client = container_client.get_blob_client("large-blob.vhd") |
| 231 | + poller = await blob_client.begin_copy_from_url(source_url) |
| 232 | + |
| 233 | + # Wait for async completion |
| 234 | + copy_properties = await poller.result() |
| 235 | +``` |
| 236 | + |
| 237 | +### Polling objects for long-running operations example: Virtual Machines |
| 238 | + |
| 239 | +Deploying Virtual Machines in an example of an operation that takes time to complete and handles it by returning poller objects (LROPoller for synchronous code, AsyncLROPoller for asynchronous code): |
| 240 | + |
| 241 | +```python |
| 242 | +from azure.mgmt.compute import ComputeManagementClient |
| 243 | +from azure.core.polling import LROPoller |
| 244 | + |
| 245 | +compute_client = ComputeManagementClient(credential, subscription_id) |
| 246 | + |
| 247 | +# Start VM creation - returns immediately with a poller |
| 248 | +poller: LROPoller = compute_client.virtual_machines.begin_create_or_update( |
| 249 | + resource_group_name="myresourcegroup", |
| 250 | + vm_name="myvm", |
| 251 | + parameters=vm_parameters |
| 252 | +) |
| 253 | + |
| 254 | +# Wait for completion and get the result |
| 255 | +vm = poller.result() # Blocks until operation completes |
| 256 | +print(f"VM {vm.name} provisioned successfully") |
| 257 | +``` |
| 258 | + |
| 259 | +### Accessing response for paged results |
| 260 | + |
| 261 | +For paged results, use the `by_page()` method with `raw_response_hook`: |
| 262 | + |
| 263 | +```python |
| 264 | +def page_response_hook(response): |
| 265 | + continuation_token = response.http_response.headers.get('x-ms-continuation') |
| 266 | + print(f"Continuation token: {continuation_token}") |
| 267 | + return response |
| 268 | + |
| 269 | +blobs = container_client.list_blobs() |
| 270 | +for page in blobs.by_page(raw_response_hook=page_response_hook): |
| 271 | + for blob in page: |
| 272 | + print(blob.name) |
| 273 | +``` |
| 274 | + |
| 275 | +## Best practices |
| 276 | + |
| 277 | +- **Prefer high-level abstractions** |
| 278 | +- **Work with the SDK's resource models rather than raw responses** whenever possible, and **avoid accessing any method prefixed with an underscore `_`** since, by convention, those are private in Python. There are no guarantees about breaking changes, etc. compared to public APIs: |
| 279 | + |
| 280 | + ```python |
| 281 | + # Preferred: Work with typed objects |
| 282 | + secret = secret_client.get_secret("mysecret") |
| 283 | + if secret.properties.enabled: |
| 284 | + use_secret(secret.value) |
| 285 | + |
| 286 | + # Avoid: Manual JSON parsing (unless necessary) ... |
| 287 | + # AND avoid accessing any objects or methods that start with `_` |
| 288 | + response = secret_client._client.get(...) # Don't access internal clients |
| 289 | + data = json.loads(response.text) |
| 290 | + if data['attributes']['enabled']: |
| 291 | + use_secret(data['value']) |
| 292 | + ``` |
| 293 | + |
| 294 | + |
| 295 | + |
| 296 | +- **Handle pagination properly** - Always iterate over paged results instead of converting to a list: |
| 297 | + |
| 298 | + ```python |
| 299 | + # Good: Memory-efficient iteration |
| 300 | + for blob in container_client.list_blobs(): |
| 301 | + process_blob(blob) |
| 302 | + |
| 303 | + # Avoid: Loading everything into memory |
| 304 | + all_blobs = list(container_client.list_blobs()) # Could consume excessive memory |
| 305 | + ``` |
| 306 | + |
| 307 | +- **Use `poller.result()` for long-running operations** - Always use the `result()` method to ensure operations complete successfully: |
| 308 | + |
| 309 | + ```python |
| 310 | + # Correct: Wait for operation completion |
| 311 | + poller = compute_client.virtual_machines.begin_delete( |
| 312 | + resource_group_name="myresourcegroup", |
| 313 | + vm_name="myvm" |
| 314 | + ) |
| 315 | + poller.result() # Ensures deletion completes |
| 316 | + print("VM deleted successfully") |
| 317 | + |
| 318 | + # Wrong: Assuming immediate completion |
| 319 | + poller = compute_client.virtual_machines.begin_delete(...) |
| 320 | + print("VM deleted successfully") # Deletion might still be in progress! |
| 321 | + ``` |
| 322 | + |
| 323 | +- **Access raw responses only when needed** - Use raw response access sparingly and only for specific requirements: |
| 324 | + |
| 325 | + ```python |
| 326 | + # Good use case: Debugging or logging |
| 327 | + def log_request_id(response): |
| 328 | + request_id = response.http_response.headers.get('x-ms-request-id') |
| 329 | + logger.info(f"Operation request ID: {request_id}") |
| 330 | + return response |
| 331 | + |
| 332 | + blob_client.upload_blob(data, raw_response_hook=log_request_id) |
| 333 | + |
| 334 | + # Good use case: Custom error handling |
| 335 | + def check_custom_header(response): |
| 336 | + if response.http_response.headers.get('x-custom-error'): |
| 337 | + raise CustomApplicationError("Custom error condition detected") |
| 338 | + return response |
| 339 | + ``` |
0 commit comments