0
# Waiters
1
2
Resource state waiters that poll AWS services until resources reach desired states, with configurable polling intervals and timeout handling. Waiters provide a reliable mechanism to wait for asynchronous operations to complete, such as EC2 instances becoming available or DynamoDB tables reaching active status.
3
4
## Capabilities
5
6
### Waiter Access
7
8
Access waiters through AWS service clients to wait for resource state changes.
9
10
```python { .api }
11
class BaseClient:
12
def get_waiter(self, waiter_name: str) -> Waiter:
13
"""
14
Get a waiter instance for polling resource states.
15
16
Args:
17
waiter_name: Name of the waiter (e.g., 'instance_running', 'bucket_exists')
18
19
Returns:
20
Waiter: Configured waiter instance for the specified resource state
21
22
Raises:
23
ValueError: If waiter_name is not available for the service
24
"""
25
26
@property
27
def waiter_names(self) -> List[str]:
28
"""
29
List of available waiter names for this service.
30
31
Returns:
32
List[str]: Available waiter names
33
"""
34
```
35
36
### Core Waiter Class
37
38
Primary waiter implementation that handles polling and state transitions.
39
40
```python { .api }
41
class Waiter:
42
def __init__(
43
self,
44
name: str,
45
config: SingleWaiterConfig,
46
operation_method: callable
47
):
48
"""
49
Initialize a waiter instance.
50
51
Args:
52
name: Waiter name identifier
53
config: Waiter configuration and acceptor rules
54
operation_method: AWS operation method to call for polling
55
"""
56
57
@property
58
def name(self) -> str:
59
"""
60
Waiter name identifier.
61
62
Returns:
63
str: The name of this waiter
64
"""
65
66
@property
67
def config(self) -> SingleWaiterConfig:
68
"""
69
Waiter configuration including polling settings and acceptors.
70
71
Returns:
72
SingleWaiterConfig: Configuration object with delay, max_attempts, and acceptors
73
"""
74
75
def wait(self, **kwargs) -> None:
76
"""
77
Wait for the resource to reach the desired state.
78
79
Args:
80
**kwargs: Parameters to pass to the underlying AWS operation
81
WaiterConfig: Optional waiter configuration overrides
82
83
Raises:
84
WaiterError: If waiter times out, encounters failure state, or receives errors
85
"""
86
```
87
88
### Waiter Configuration
89
90
Configure waiter behavior with custom timing and retry settings.
91
92
```python { .api }
93
class WaiterModel:
94
SUPPORTED_VERSION: int = 2
95
96
def __init__(self, waiter_config: dict):
97
"""
98
Initialize waiter model from service configuration.
99
100
Args:
101
waiter_config: Loaded waiter configuration from service model
102
103
Raises:
104
WaiterConfigError: If waiter version is unsupported
105
"""
106
107
@property
108
def version(self) -> int:
109
"""
110
Waiter configuration format version.
111
112
Returns:
113
int: Configuration version (always 2 for supported models)
114
"""
115
116
@property
117
def waiter_names(self) -> List[str]:
118
"""
119
Available waiter names in this model.
120
121
Returns:
122
List[str]: Sorted list of waiter names
123
"""
124
125
def get_waiter(self, waiter_name: str) -> SingleWaiterConfig:
126
"""
127
Get configuration for a specific waiter.
128
129
Args:
130
waiter_name: Name of the waiter to retrieve
131
132
Returns:
133
SingleWaiterConfig: Waiter configuration object
134
135
Raises:
136
ValueError: If waiter_name does not exist
137
"""
138
139
class SingleWaiterConfig:
140
def __init__(self, single_waiter_config: dict):
141
"""
142
Initialize single waiter configuration.
143
144
Args:
145
single_waiter_config: Configuration dictionary for one waiter
146
"""
147
148
@property
149
def description(self) -> str:
150
"""
151
Human-readable waiter description.
152
153
Returns:
154
str: Waiter description text
155
"""
156
157
@property
158
def operation(self) -> str:
159
"""
160
AWS operation name used for polling.
161
162
Returns:
163
str: Operation name (e.g., 'DescribeInstances', 'HeadBucket')
164
"""
165
166
@property
167
def delay(self) -> int:
168
"""
169
Default delay between polling attempts in seconds.
170
171
Returns:
172
int: Delay in seconds
173
"""
174
175
@property
176
def max_attempts(self) -> int:
177
"""
178
Maximum number of polling attempts before timeout.
179
180
Returns:
181
int: Maximum attempts count
182
"""
183
184
@property
185
def acceptors(self) -> List[AcceptorConfig]:
186
"""
187
List of acceptor rules that determine state transitions.
188
189
Returns:
190
List[AcceptorConfig]: Acceptor configurations
191
"""
192
193
class AcceptorConfig:
194
def __init__(self, config: dict):
195
"""
196
Initialize acceptor configuration.
197
198
Args:
199
config: Acceptor configuration dictionary
200
"""
201
202
@property
203
def state(self) -> str:
204
"""
205
Target state for this acceptor ('success', 'failure', 'retry').
206
207
Returns:
208
str: State transition target
209
"""
210
211
@property
212
def matcher(self) -> str:
213
"""
214
Matcher type ('path', 'pathAll', 'pathAny', 'status', 'error').
215
216
Returns:
217
str: Matcher type identifier
218
"""
219
220
@property
221
def expected(self) -> Any:
222
"""
223
Expected value for successful match.
224
225
Returns:
226
Any: Expected value (string, int, bool, etc.)
227
"""
228
229
@property
230
def argument(self) -> str:
231
"""
232
JMESPath expression or matcher argument.
233
234
Returns:
235
str: JMESPath expression for path matchers
236
"""
237
238
@property
239
def explanation(self) -> str:
240
"""
241
Human-readable explanation of what this acceptor matches.
242
243
Returns:
244
str: Explanation text for debugging
245
"""
246
247
@property
248
def matcher_func(self) -> callable:
249
"""
250
Compiled matcher function that evaluates responses.
251
252
Returns:
253
callable: Function that takes response dict and returns bool
254
"""
255
```
256
257
## Usage Examples
258
259
### Basic Waiter Usage
260
261
Wait for AWS resources to reach desired states using service-specific waiters.
262
263
```python
264
from botocore.session import get_session
265
266
# Create session and clients
267
session = get_session()
268
ec2_client = session.create_client('ec2', region_name='us-east-1')
269
s3_client = session.create_client('s3', region_name='us-east-1')
270
271
# Wait for EC2 instance to be running
272
instance_id = 'i-1234567890abcdef0'
273
waiter = ec2_client.get_waiter('instance_running')
274
waiter.wait(InstanceIds=[instance_id])
275
print(f"Instance {instance_id} is now running")
276
277
# Wait for S3 bucket to exist
278
bucket_name = 'my-example-bucket'
279
waiter = s3_client.get_waiter('bucket_exists')
280
waiter.wait(Bucket=bucket_name)
281
print(f"Bucket {bucket_name} exists and is accessible")
282
```
283
284
### Custom Waiter Configuration
285
286
Override default polling behavior with custom timing settings.
287
288
```python
289
# Wait with custom configuration
290
waiter = ec2_client.get_waiter('instance_stopped')
291
waiter.wait(
292
InstanceIds=['i-1234567890abcdef0'],
293
WaiterConfig={
294
'Delay': 10, # Poll every 10 seconds instead of default
295
'MaxAttempts': 60 # Try up to 60 times instead of default
296
}
297
)
298
```
299
300
### DynamoDB Table Waiters
301
302
Wait for DynamoDB table operations to complete.
303
304
```python
305
dynamodb_client = session.create_client('dynamodb', region_name='us-east-1')
306
307
# Create table and wait for it to become active
308
dynamodb_client.create_table(
309
TableName='MyTable',
310
KeySchema=[
311
{'AttributeName': 'id', 'KeyType': 'HASH'}
312
],
313
AttributeDefinitions=[
314
{'AttributeName': 'id', 'AttributeType': 'S'}
315
],
316
BillingMode='PAY_PER_REQUEST'
317
)
318
319
# Wait for table to be ready
320
waiter = dynamodb_client.get_waiter('table_exists')
321
waiter.wait(TableName='MyTable')
322
print("Table is now active and ready to use")
323
324
# Delete table and wait for deletion to complete
325
dynamodb_client.delete_table(TableName='MyTable')
326
waiter = dynamodb_client.get_waiter('table_not_exists')
327
waiter.wait(TableName='MyTable')
328
print("Table has been successfully deleted")
329
```
330
331
### CloudFormation Stack Waiters
332
333
Monitor CloudFormation stack operations until completion.
334
335
```python
336
cloudformation_client = session.create_client('cloudformation', region_name='us-east-1')
337
338
# Create stack and wait for completion
339
cloudformation_client.create_stack(
340
StackName='my-stack',
341
TemplateBody=template_content,
342
Parameters=[
343
{'ParameterKey': 'Environment', 'ParameterValue': 'production'}
344
]
345
)
346
347
# Wait for stack creation to complete
348
waiter = cloudformation_client.get_waiter('stack_create_complete')
349
waiter.wait(StackName='my-stack')
350
print("Stack creation completed successfully")
351
352
# Update stack and wait for update completion
353
cloudformation_client.update_stack(
354
StackName='my-stack',
355
TemplateBody=updated_template_content
356
)
357
358
waiter = cloudformation_client.get_waiter('stack_update_complete')
359
waiter.wait(StackName='my-stack')
360
print("Stack update completed successfully")
361
```
362
363
### Discovering Available Waiters
364
365
Find out which waiters are available for a service.
366
367
```python
368
# List all available waiters for a service
369
ec2_waiters = ec2_client.waiter_names
370
print(f"EC2 waiters: {ec2_waiters}")
371
372
s3_waiters = s3_client.waiter_names
373
print(f"S3 waiters: {s3_waiters}")
374
375
# Check if specific waiter exists
376
if 'instance_running' in ec2_client.waiter_names:
377
waiter = ec2_client.get_waiter('instance_running')
378
print(f"Waiter config: delay={waiter.config.delay}s, max_attempts={waiter.config.max_attempts}")
379
```
380
381
### Error Handling
382
383
Handle waiter timeouts and failure conditions properly.
384
385
```python
386
from botocore.exceptions import WaiterError
387
388
try:
389
waiter = ec2_client.get_waiter('instance_running')
390
waiter.wait(
391
InstanceIds=['i-1234567890abcdef0'],
392
WaiterConfig={'MaxAttempts': 10}
393
)
394
except WaiterError as e:
395
print(f"Waiter failed: {e.reason}")
396
print(f"Last response: {e.last_response}")
397
398
# Handle specific failure cases
399
if 'Max attempts exceeded' in e.reason:
400
print("Instance took too long to start")
401
elif 'terminal failure state' in e.reason:
402
print("Instance failed to start properly")
403
```
404
405
### Lambda Function Waiters
406
407
Wait for Lambda function states and configurations.
408
409
```python
410
lambda_client = session.create_client('lambda', region_name='us-east-1')
411
412
# Wait for function to be active after creation
413
waiter = lambda_client.get_waiter('function_active')
414
waiter.wait(FunctionName='my-function')
415
print("Lambda function is active")
416
417
# Wait for function configuration update to complete
418
lambda_client.update_function_configuration(
419
FunctionName='my-function',
420
Runtime='python3.9',
421
Handler='lambda_function.lambda_handler'
422
)
423
424
waiter = lambda_client.get_waiter('function_updated')
425
waiter.wait(FunctionName='my-function')
426
print("Function configuration update completed")
427
```
428
429
## Common Waiter Types
430
431
### EC2 Instance Waiters
432
433
- `instance_exists`: Instance appears in describe_instances
434
- `instance_running`: Instance reaches 'running' state
435
- `instance_stopped`: Instance reaches 'stopped' state
436
- `instance_terminated`: Instance reaches 'terminated' state
437
- `instance_status_ok`: Instance passes status checks
438
439
### S3 Bucket Waiters
440
441
- `bucket_exists`: Bucket is accessible via HEAD request
442
- `bucket_not_exists`: Bucket no longer exists or is inaccessible
443
- `object_exists`: Object exists in bucket
444
- `object_not_exists`: Object no longer exists in bucket
445
446
### RDS Instance Waiters
447
448
- `db_instance_available`: Database instance is available
449
- `db_instance_deleted`: Database instance has been deleted
450
- `db_snapshot_available`: Database snapshot is available
451
- `db_snapshot_deleted`: Database snapshot has been deleted
452
453
### ELB Waiters
454
455
- `any_instance_in_service`: At least one instance is in service
456
- `instance_deregistered`: Instance is no longer registered
457
- `instance_in_service`: Specific instance is in service
458
459
## Waiter Configuration
460
461
### Timing Configuration
462
463
Control polling behavior through waiter configuration parameters.
464
465
```python
466
waiter_config = {
467
'Delay': 5, # Seconds to wait between attempts
468
'MaxAttempts': 40 # Maximum number of polling attempts
469
}
470
471
waiter.wait(ResourceId='resource-123', WaiterConfig=waiter_config)
472
```
473
474
### Acceptor Types
475
476
Waiters use different matcher types to evaluate responses:
477
478
- **path**: JMESPath expression matches expected value exactly
479
- **pathAll**: All elements in JMESPath result match expected value
480
- **pathAny**: At least one element in JMESPath result matches expected value
481
- **status**: HTTP status code matches expected value
482
- **error**: AWS error code matches expected value (or checks error presence/absence)
483
484
### Custom Waiter Creation
485
486
Create custom waiters using the waiter model system.
487
488
```python
489
from botocore.waiter import WaiterModel, create_waiter_with_client
490
491
# Define custom waiter configuration
492
waiter_config = {
493
'version': 2,
494
'waiters': {
495
'CustomResourceReady': {
496
'delay': 10,
497
'maxAttempts': 30,
498
'operation': 'DescribeCustomResource',
499
'acceptors': [
500
{
501
'matcher': 'path',
502
'expected': 'READY',
503
'argument': 'ResourceStatus',
504
'state': 'success'
505
},
506
{
507
'matcher': 'path',
508
'expected': 'FAILED',
509
'argument': 'ResourceStatus',
510
'state': 'failure'
511
}
512
]
513
}
514
}
515
}
516
517
# Create waiter model and waiter instance
518
waiter_model = WaiterModel(waiter_config)
519
custom_waiter = create_waiter_with_client('CustomResourceReady', waiter_model, client)
520
521
# Use custom waiter
522
custom_waiter.wait(ResourceId='resource-123')
523
```
524
525
## Best Practices
526
527
### Timeout Management
528
529
Always configure appropriate timeouts for your use case.
530
531
```python
532
# For long-running operations, increase max attempts
533
waiter.wait(
534
ResourceId='large-resource',
535
WaiterConfig={
536
'Delay': 30, # Check every 30 seconds
537
'MaxAttempts': 120 # Wait up to 1 hour (30s × 120 = 3600s)
538
}
539
)
540
```
541
542
### Error Recovery
543
544
Implement proper error handling and retry logic.
545
546
```python
547
import time
548
from botocore.exceptions import WaiterError
549
550
def wait_with_retry(waiter, max_retries=3, **kwargs):
551
"""Wait with exponential backoff retry on failures."""
552
for attempt in range(max_retries + 1):
553
try:
554
waiter.wait(**kwargs)
555
return True
556
except WaiterError as e:
557
if attempt == max_retries:
558
raise
559
if 'Max attempts exceeded' in e.reason:
560
# Exponential backoff before retry
561
delay = 2 ** attempt * 60 # 1min, 2min, 4min
562
time.sleep(delay)
563
else:
564
# Don't retry on terminal failures
565
raise
566
return False
567
```
568
569
### Resource Cleanup
570
571
Use waiters to ensure proper resource cleanup.
572
573
```python
574
def cleanup_resources(ec2_client, instance_ids):
575
"""Safely terminate instances and wait for cleanup."""
576
# Terminate instances
577
ec2_client.terminate_instances(InstanceIds=instance_ids)
578
579
# Wait for termination to complete
580
waiter = ec2_client.get_waiter('instance_terminated')
581
waiter.wait(InstanceIds=instance_ids)
582
583
print(f"All instances {instance_ids} have been terminated")
584
```
585
586
### Monitoring Progress
587
588
Combine waiters with logging to monitor long-running operations.
589
590
```python
591
import logging
592
593
logger = logging.getLogger(__name__)
594
595
def wait_with_progress(waiter, operation_name, **kwargs):
596
"""Wait with progress logging."""
597
config = kwargs.get('WaiterConfig', {})
598
max_attempts = config.get('MaxAttempts', waiter.config.max_attempts)
599
delay = config.get('Delay', waiter.config.delay)
600
601
logger.info(f"Starting {operation_name} - will check every {delay}s for up to {max_attempts} attempts")
602
603
try:
604
waiter.wait(**kwargs)
605
logger.info(f"{operation_name} completed successfully")
606
except WaiterError as e:
607
logger.error(f"{operation_name} failed: {e.reason}")
608
raise
609
```