Declarative expect/wait syntax for AWS services (S3, DynamoDB, and more)
Project description
AWS Expect
Declarative, Pythonic waiters for AWS services using boto3 resources.
Features
- ๐ฏ Declarative syntax:
expect_s3(obj).to_exist(timeout=30) - ๐ Native boto3 waiters: Uses AWS's built-in waiter infrastructure
- ๐งช Testing-friendly: Perfect for integration tests and CI/CD
- ๐ฆ Resource-based: Works with boto3 resource objects (not low-level clients)
- โฑ๏ธ Flexible timeouts: Configure both timeout and poll intervals
Installation
pip install aws-expect
Or with uv:
uv add aws-expect
Quick Start
S3 Object Waiting
import boto3
from aws_expect import expect_s3, S3WaitTimeoutError
# Create S3 resource and object
s3 = boto3.resource("s3")
obj = s3.Object("my-bucket", "report.csv")
# Wait for object to exist (returns metadata)
try:
metadata = expect_s3(obj).to_exist(timeout=30, poll_interval=5)
print(f"Object exists! Size: {metadata['ContentLength']} bytes")
except S3WaitTimeoutError:
print("Object did not appear within 30 seconds")
# Wait for object to be deleted
expect_s3(obj).to_not_exist(timeout=10, poll_interval=2)
DynamoDB Item Waiting
import boto3
from aws_expect import expect_dynamodb, DynamoDBWaitTimeoutError
dynamodb = boto3.resource("dynamodb")
table = dynamodb.Table("orders")
# Wait for an item to exist (by primary key)
try:
item = expect_dynamodb(table).to_exist(
key={"pk": "order-123"},
timeout=30,
poll_interval=5,
)
print(f"Order found: {item}")
except DynamoDBWaitTimeoutError:
print("Item did not appear within 30 seconds")
# Wait for an item with specific field values (subset match)
item = expect_dynamodb(table).to_exist(
key={"pk": "order-123"},
entries={"status": "shipped"},
timeout=60,
poll_interval=5,
)
# Composite key (partition + sort key)
item = expect_dynamodb(table).to_exist(
key={"pk": "user-1", "sk": "order-456"},
timeout=30,
)
# Wait for an item to be deleted
expect_dynamodb(table).to_not_exist(key={"pk": "order-123"}, timeout=10)
Catching Any Timeout
All service-specific exceptions inherit from WaitTimeoutError, so you can
catch timeouts from any service in a single handler:
from aws_expect import expect_s3, WaitTimeoutError
try:
expect_s3(obj).to_exist(timeout=30)
except WaitTimeoutError:
print("Timed out waiting for resource")
API Reference
expect_s3(s3_object)
Creates an S3ObjectExpectation wrapper for an S3 resource Object.
Parameters:
s3_object: Aboto3.resource("s3").Object(bucket, key)instance
Returns: S3ObjectExpectation
expect_dynamodb(table)
Creates a DynamoDBItemExpectation wrapper for a DynamoDB Table resource.
Parameters:
table: Aboto3.resource("dynamodb").Table(name)instance
Returns: DynamoDBItemExpectation
S3ObjectExpectation.to_exist(timeout=30, poll_interval=5)
Wait for the S3 object to exist using the native object_exists waiter.
Parameters:
timeout(float): Maximum time in seconds to wait (default: 30)poll_interval(float): Time in seconds between polling attempts, minimum 1 (default: 5)
Returns: dict[str, Any] โ The head_object response metadata
Raises: S3WaitTimeoutError โ If the object does not exist within timeout
Example:
metadata = expect_s3(obj).to_exist(timeout=60, poll_interval=10)
print(metadata["ETag"], metadata["ContentType"])
S3ObjectExpectation.to_not_exist(timeout=30, poll_interval=5)
Wait for the S3 object to not exist (be deleted) using the native object_not_exists waiter.
Parameters:
timeout(float): Maximum time in seconds to wait (default: 30)poll_interval(float): Time in seconds between polling attempts, minimum 1 (default: 5)
Returns: None
Raises: S3WaitTimeoutError โ If the object still exists after timeout
DynamoDBItemExpectation.to_exist(key, timeout=30, poll_interval=5, entries=None)
Poll get_item until the item exists and optionally matches the expected entries.
Parameters:
key(dict[str, Any]): Primary key dict, e.g.{"pk": "val"}or{"pk": "val", "sk": "val"}timeout(float): Maximum time in seconds to wait (default: 30)poll_interval(float): Time in seconds between polling attempts, minimum 1 (default: 5)entries(dict[str, Any] | None): Optional expected key-value pairs. When provided, the item must contain at least these entries (subset match) before the wait succeeds.
Returns: dict[str, Any] โ The full item from DynamoDB
Raises: DynamoDBWaitTimeoutError โ If the item does not exist or does not match entries within timeout
Example:
item = expect_dynamodb(table).to_exist(
key={"pk": "order-1"},
entries={"status": "shipped"},
timeout=60,
)
print(item["status"], item["total"])
DynamoDBItemExpectation.to_not_exist(key, timeout=30, poll_interval=5)
Poll get_item until the item no longer exists.
Parameters:
key(dict[str, Any]): Primary key dicttimeout(float): Maximum time in seconds to wait (default: 30)poll_interval(float): Time in seconds between polling attempts, minimum 1 (default: 5)
Returns: None
Raises: DynamoDBWaitTimeoutError โ If the item still exists after timeout
Exceptions
WaitTimeoutError
Base exception for all wait timeout errors. Catch this to handle timeouts from any AWS service.
Attributes:
timeout(float): The timeout value that was exceeded
S3WaitTimeoutError(WaitTimeoutError)
Raised when an S3 wait operation exceeds the specified timeout.
Attributes:
bucket(str): The S3 bucket namekey(str): The S3 object keytimeout(float): The timeout value that was exceeded
DynamoDBWaitTimeoutError(WaitTimeoutError)
Raised when a DynamoDB wait operation exceeds the specified timeout.
Attributes:
table_name(str): The DynamoDB table namekey(dict[str, str]): The primary key that was being waited ontimeout(float): The timeout value that was exceeded
How It Works
S3 โ uses boto3's native waiter infrastructure:
to_exist()โclient.get_waiter("object_exists").wait()to_not_exist()โclient.get_waiter("object_not_exists").wait()
DynamoDB โ uses a custom polling loop over get_item (DynamoDB does not provide built-in item-level waiters):
to_exist()โ pollstable.get_item(Key=...)until the item appears and optionally matches expected entriesto_not_exist()โ pollstable.get_item(Key=...)until the item is gone
Development
Setup
# Clone the repository
git clone https://github.com/PhishStick-hub/aws-expect
cd aws-expect
# Install with dev dependencies
uv sync --all-groups
Running Tests
Tests use testcontainers and LocalStack for real AWS API simulation:
# Ensure Docker is running
docker info
# Run tests
uv run pytest tests/ -v
Project Structure
aws-expect/
โโโ aws_expect/
โ โโโ __init__.py # Public API exports
โ โโโ exceptions.py # WaitTimeoutError hierarchy
โ โโโ expect.py # expect_s3(), expect_dynamodb()
โ โโโ dynamodb.py # DynamoDBItemExpectation
โ โโโ s3.py # S3ObjectExpectation
โโโ tests/
โ โโโ conftest.py # LocalStack fixtures
โ โโโ test_dynamodb_item.py
โ โโโ test_s3_exist.py
โ โโโ test_s3_not_exist.py
โโโ pyproject.toml
Future Roadmap
- DynamoDB item waiters
- Lambda function readiness waiters
- More S3 matchers (content-type, size, tags)
License
MIT License - see LICENSE file for details
Author
Ivan Shcherbenko
Credits
Built with:
- boto3 โ AWS SDK for Python
- testcontainers-python โ Testing with real services
- LocalStack โ Local AWS cloud stack
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file aws_expect-0.1.1.tar.gz.
File metadata
- Download URL: aws_expect-0.1.1.tar.gz
- Upload date:
- Size: 36.8 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.10.4 {"installer":{"name":"uv","version":"0.10.4","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
837a053f24e6b89efc813d75f7810327eeacd4c6d59f96c03cd505e7b5533f1c
|
|
| MD5 |
0564be66d05d72fa9eb647d755a9885d
|
|
| BLAKE2b-256 |
6b2f8ca58d7e01451e1c66b94254b88cfd99b0ed06fb411d968696acb37ccbf0
|
File details
Details for the file aws_expect-0.1.1-py3-none-any.whl.
File metadata
- Download URL: aws_expect-0.1.1-py3-none-any.whl
- Upload date:
- Size: 9.3 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.10.4 {"installer":{"name":"uv","version":"0.10.4","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
260ead2f62feaf94480e53adc717a9d4572ac1a784ea8f88ee8bdc5359d03c81
|
|
| MD5 |
249268b6f6fb9572f2acad2f42953d57
|
|
| BLAKE2b-256 |
084c945519887738650742970e88daaebaa03e9a8c506e2be55e09d755c08e5f
|