Skip to main content

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: A boto3.resource("s3").Object(bucket, key) instance

Returns: S3ObjectExpectation


expect_dynamodb(table)

Creates a DynamoDBItemExpectation wrapper for a DynamoDB Table resource.

Parameters:

  • table: A boto3.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 dict
  • 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: 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 name
  • key (str): The S3 object key
  • timeout (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 name
  • key (dict[str, str]): The primary key that was being waited on
  • timeout (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() โ†’ polls table.get_item(Key=...) until the item appears and optionally matches expected entries
  • to_not_exist() โ†’ polls table.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:

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

aws_expect-0.1.1.tar.gz (36.8 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

aws_expect-0.1.1-py3-none-any.whl (9.3 kB view details)

Uploaded Python 3

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

Hashes for aws_expect-0.1.1.tar.gz
Algorithm Hash digest
SHA256 837a053f24e6b89efc813d75f7810327eeacd4c6d59f96c03cd505e7b5533f1c
MD5 0564be66d05d72fa9eb647d755a9885d
BLAKE2b-256 6b2f8ca58d7e01451e1c66b94254b88cfd99b0ed06fb411d968696acb37ccbf0

See more details on using hashes here.

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

Hashes for aws_expect-0.1.1-py3-none-any.whl
Algorithm Hash digest
SHA256 260ead2f62feaf94480e53adc717a9d4572ac1a784ea8f88ee8bdc5359d03c81
MD5 249268b6f6fb9572f2acad2f42953d57
BLAKE2b-256 084c945519887738650742970e88daaebaa03e9a8c506e2be55e09d755c08e5f

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page