0
# Long-Running Operations
1
2
Polling system for long-running operations with configurable retry strategies, timeout handling, and completion detection for Azure ARM and other async API patterns. The LRO (Long-Running Operation) system handles operations that don't complete immediately.
3
4
## Capabilities
5
6
### LRO Poller
7
8
Main class for polling long-running operations until completion.
9
10
```python { .api }
11
class LROPoller:
12
def result(self, timeout=None):
13
"""
14
Get the result of the long-running operation.
15
16
Parameters:
17
- timeout: Maximum time to wait in seconds (None for no timeout)
18
19
Returns:
20
Final result of the operation
21
22
Raises:
23
TimeoutError if operation doesn't complete within timeout
24
"""
25
26
def wait(self, timeout=None):
27
"""
28
Wait for operation to complete without returning result.
29
30
Parameters:
31
- timeout: Maximum time to wait in seconds
32
"""
33
34
def done(self) -> bool:
35
"""
36
Check if operation is complete.
37
38
Returns:
39
True if operation is finished, False otherwise
40
"""
41
42
def add_done_callback(self, func):
43
"""
44
Add callback to execute when operation completes.
45
46
Parameters:
47
- func: Callback function to execute
48
"""
49
50
def remove_done_callback(self, func):
51
"""
52
Remove previously added callback.
53
54
Parameters:
55
- func: Callback function to remove
56
"""
57
58
def status(self) -> str:
59
"""
60
Get current operation status.
61
62
Returns:
63
Status string (e.g., 'InProgress', 'Succeeded', 'Failed')
64
"""
65
```
66
67
### Polling Methods
68
69
Base class and implementations for different polling strategies.
70
71
```python { .api }
72
class PollingMethod:
73
def initialize(self, client, initial_response, deserialization_callback):
74
"""
75
Initialize polling method with initial response.
76
77
Parameters:
78
- client: HTTP client for making requests
79
- initial_response: Initial operation response
80
- deserialization_callback: Function to deserialize responses
81
"""
82
83
def run(self) -> bool:
84
"""
85
Execute one polling iteration.
86
87
Returns:
88
True if operation is complete, False to continue polling
89
"""
90
91
def status(self) -> str:
92
"""Get current operation status."""
93
94
def finished(self) -> bool:
95
"""Check if operation is finished."""
96
97
def resource(self):
98
"""Get final resource/result."""
99
100
class NoPolling(PollingMethod):
101
"""
102
No-op polling method that considers operation immediately complete.
103
Used for operations that complete synchronously.
104
"""
105
```
106
107
### Async Polling (Python 3.5+)
108
109
Async versions of polling classes for asynchronous operations.
110
111
```python { .api }
112
class AsyncPollingMethod:
113
async def run(self) -> bool:
114
"""Execute one polling iteration asynchronously."""
115
116
def initialize(self, client, initial_response, deserialization_callback):
117
"""Initialize async polling method."""
118
119
class AsyncNoPolling(AsyncPollingMethod):
120
"""Async no-op polling method."""
121
122
def async_poller(client, initial_response, deserialization_callback, polling_method):
123
"""
124
Create async poller for long-running operations.
125
126
Parameters:
127
- client: Async HTTP client
128
- initial_response: Initial operation response
129
- deserialization_callback: Response deserialization function
130
- polling_method: Async polling method instance
131
132
Returns:
133
Async poller object
134
"""
135
```
136
137
## Usage Examples
138
139
### Basic Long-Running Operation
140
141
```python
142
from msrest import ServiceClient, Configuration
143
from msrest.polling import LROPoller
144
145
# Setup client
146
config = Configuration(base_url='https://api.example.com')
147
client = ServiceClient(None, config)
148
149
# Start long-running operation
150
request = client.post('/start-operation', content='{"data": "value"}')
151
initial_response = client.send(request)
152
153
# Create poller (assuming service provides one)
154
# This is typically done by the SDK, not manually
155
poller = LROPoller(client, initial_response, deserialization_callback)
156
157
# Wait for completion and get result
158
try:
159
result = poller.result(timeout=300) # 5 minute timeout
160
print(f"Operation completed: {result}")
161
except TimeoutError:
162
print("Operation timed out")
163
```
164
165
### Polling with Status Checks
166
167
```python
168
from msrest.polling import LROPoller
169
import time
170
171
# Start operation and get poller
172
poller = start_long_running_operation()
173
174
# Poll manually with status checks
175
while not poller.done():
176
status = poller.status()
177
print(f"Operation status: {status}")
178
179
if status == 'Failed':
180
print("Operation failed")
181
break
182
elif status == 'Cancelled':
183
print("Operation was cancelled")
184
break
185
186
# Wait before next check
187
time.sleep(10)
188
189
if poller.done():
190
result = poller.result()
191
print(f"Final result: {result}")
192
```
193
194
### Using Callbacks
195
196
```python
197
from msrest.polling import LROPoller
198
199
def operation_completed(poller):
200
"""Callback function executed when operation completes."""
201
try:
202
result = poller.result()
203
print(f"Operation finished successfully: {result}")
204
except Exception as e:
205
print(f"Operation failed: {e}")
206
207
def operation_progress(poller):
208
"""Callback for progress updates."""
209
status = poller.status()
210
print(f"Progress update: {status}")
211
212
# Create poller and add callbacks
213
poller = start_long_running_operation()
214
poller.add_done_callback(operation_completed)
215
216
# Wait for completion (callbacks will be called)
217
poller.wait(timeout=600) # 10 minute timeout
218
```
219
220
### Custom Polling Method
221
222
```python
223
from msrest.polling import PollingMethod
224
import time
225
import json
226
227
class CustomPollingMethod(PollingMethod):
228
"""Custom polling method for specific API pattern."""
229
230
def __init__(self, timeout=30, interval=5):
231
super(CustomPollingMethod, self).__init__()
232
self.timeout = timeout
233
self.interval = interval
234
self._status = 'InProgress'
235
self._finished = False
236
self._resource = None
237
238
def initialize(self, client, initial_response, deserialization_callback):
239
"""Initialize with API-specific logic."""
240
self._client = client
241
self._deserialize = deserialization_callback
242
243
# Parse initial response for operation URL
244
response_data = json.loads(initial_response.text)
245
self._operation_url = response_data.get('operation_url')
246
self._status = response_data.get('status', 'InProgress')
247
248
def run(self):
249
"""Check operation status."""
250
if self._finished:
251
return True
252
253
# Make status check request
254
request = self._client.get(self._operation_url)
255
response = self._client.send(request)
256
257
# Parse response
258
data = json.loads(response.text)
259
self._status = data.get('status')
260
261
if self._status in ['Succeeded', 'Completed']:
262
self._finished = True
263
self._resource = self._deserialize('OperationResult', response)
264
return True
265
elif self._status in ['Failed', 'Cancelled']:
266
self._finished = True
267
return True
268
269
# Still in progress
270
time.sleep(self.interval)
271
return False
272
273
def status(self):
274
return self._status
275
276
def finished(self):
277
return self._finished
278
279
def resource(self):
280
return self._resource
281
282
# Use custom polling method
283
custom_method = CustomPollingMethod(timeout=300, interval=10)
284
poller = LROPoller(client, initial_response, custom_method)
285
286
result = poller.result()
287
```
288
289
### Async Long-Running Operations
290
291
```python
292
import asyncio
293
from msrest.polling import async_poller, AsyncNoPolling
294
295
async def async_long_running_operation():
296
"""Example async long-running operation."""
297
298
# Start async operation
299
async_client = await create_async_client()
300
initial_response = await async_client.post('/async-operation')
301
302
# Create async poller
303
polling_method = AsyncNoPolling() # or custom async method
304
poller = async_poller(
305
async_client,
306
initial_response,
307
deserialization_callback,
308
polling_method
309
)
310
311
# Wait for completion asynchronously
312
result = await poller.result(timeout=300)
313
return result
314
315
# Run async operation
316
async def main():
317
try:
318
result = await async_long_running_operation()
319
print(f"Async operation result: {result}")
320
except asyncio.TimeoutError:
321
print("Async operation timed out")
322
323
asyncio.run(main())
324
```
325
326
### Azure ARM Pattern Example
327
328
```python
329
from msrest.polling import LROPoller
330
import json
331
332
class AzureARMPollingMethod:
333
"""Polling method for Azure Resource Manager operations."""
334
335
def initialize(self, client, initial_response, deserialization_callback):
336
self._client = client
337
self._deserialize = deserialization_callback
338
339
# Azure ARM uses specific headers for polling
340
headers = initial_response.headers
341
342
# Check for Azure-AsyncOperation header (preferred)
343
if 'Azure-AsyncOperation' in headers:
344
self._polling_url = headers['Azure-AsyncOperation']
345
self._method = 'async_operation'
346
# Check for Location header (fallback)
347
elif 'Location' in headers:
348
self._polling_url = headers['Location']
349
self._method = 'location'
350
else:
351
# No polling needed, operation is complete
352
self._method = 'no_polling'
353
self._finished = True
354
return
355
356
self._finished = False
357
358
def run(self):
359
if self._finished:
360
return True
361
362
# Poll the appropriate URL
363
request = self._client.get(self._polling_url)
364
response = self._client.send(request)
365
366
if self._method == 'async_operation':
367
# Parse Azure-AsyncOperation response
368
data = json.loads(response.text)
369
status = data.get('status')
370
371
if status == 'Succeeded':
372
# Need to get final result from original Location
373
self._finished = True
374
return True
375
elif status in ['Failed', 'Cancelled']:
376
self._finished = True
377
return True
378
379
elif self._method == 'location':
380
# Location polling - check response status
381
if response.status_code == 200:
382
self._finished = True
383
return True
384
elif response.status_code == 202:
385
# Still in progress
386
return False
387
388
return False
389
390
# Use with Azure operations
391
def create_azure_resource():
392
# Start resource creation
393
request = client.put('/subscriptions/.../resourceGroups/.../providers/.../resource')
394
initial_response = client.send(request)
395
396
# Create poller with Azure-specific method
397
azure_method = AzureARMPollingMethod()
398
poller = LROPoller(client, initial_response, azure_method)
399
400
# Wait for resource creation to complete
401
result = poller.result(timeout=600) # 10 minutes
402
return result
403
```
404
405
### Error Handling with LRO
406
407
```python
408
from msrest.polling import LROPoller
409
from msrest.exceptions import HttpOperationError
410
411
def robust_long_running_operation():
412
"""LRO with comprehensive error handling."""
413
414
try:
415
# Start operation
416
poller = start_operation()
417
418
# Add error callback
419
def error_callback(poller):
420
status = poller.status()
421
if status == 'Failed':
422
print("Operation failed - checking details")
423
# Could inspect poller.result() for error details
424
425
poller.add_done_callback(error_callback)
426
427
# Wait with timeout
428
result = poller.result(timeout=300)
429
430
if poller.status() == 'Succeeded':
431
return result
432
else:
433
raise Exception(f"Operation ended with status: {poller.status()}")
434
435
except TimeoutError:
436
print("Operation timed out")
437
# Could cancel operation or continue waiting
438
raise
439
440
except HttpOperationError as e:
441
print(f"HTTP error during polling: {e.response.status_code}")
442
raise
443
444
except Exception as e:
445
print(f"Unexpected error during LRO: {e}")
446
raise
447
448
# Usage with error handling
449
try:
450
result = robust_long_running_operation()
451
print(f"Success: {result}")
452
except Exception as e:
453
print(f"LRO failed: {e}")
454
```
455
456
### Monitoring Multiple Operations
457
458
```python
459
from msrest.polling import LROPoller
460
import threading
461
import time
462
463
def monitor_multiple_operations():
464
"""Monitor multiple long-running operations concurrently."""
465
466
# Start multiple operations
467
pollers = []
468
for i in range(5):
469
poller = start_operation(f"operation_{i}")
470
pollers.append((f"operation_{i}", poller))
471
472
# Monitor all operations
473
completed = {}
474
475
while len(completed) < len(pollers):
476
for name, poller in pollers:
477
if name not in completed and poller.done():
478
try:
479
result = poller.result()
480
completed[name] = result
481
print(f"{name} completed successfully")
482
except Exception as e:
483
completed[name] = None
484
print(f"{name} failed: {e}")
485
486
# Check every 10 seconds
487
time.sleep(10)
488
489
return completed
490
491
# Run monitoring
492
results = monitor_multiple_operations()
493
print(f"All operations completed: {len(results)} total")
494
```
495
496
## Types
497
498
```python { .api }
499
# Common LRO status values
500
LRO_STATUS_IN_PROGRESS = "InProgress"
501
LRO_STATUS_SUCCEEDED = "Succeeded"
502
LRO_STATUS_FAILED = "Failed"
503
LRO_STATUS_CANCELLED = "Cancelled"
504
505
# Callback function signature
506
def lro_callback(poller: LROPoller) -> None:
507
"""
508
Callback function called when LRO completes.
509
510
Parameters:
511
- poller: The LROPoller instance that completed
512
"""
513
```