Nimble-AWS
Asynchronous, typed adapters for AWS services, built on aiobotocore and tuned for code that runs inside AWS: Lambda, ECS and Glue.
- One client per service, reused. Clients (and their HTTP/TLS connection pools) are created once per process and event loop, not on every call.
- Single and batch methods for every operation.
get_object/get_object_batch,put_item/put_item_batch,send_message/send_message_batch... Batch methods use the native batch APIs where AWS has one (DynamoDBBatchWriteItem/BatchGetItem, SQSSendMessageBatch, SNSPublishBatch, S3DeleteObjects, Secrets ManagerBatchGetSecretValue...), retry unprocessed entries and run with bounded concurrency. - No silent data loss. Partial failures come back in a
BatchResultand raisePartialBatchErrorby default. - Dynamic and typed. Every method forwards extra
**kwargsto the boto3 method it wraps, typed with the TypedDicts innimble_aws.pytypes, generated from the botocore service models (the same source as the boto3 reference).
Installation
pip install nimble-aws # adapters
pip install "nimble-aws[lambda]" # + Lambda handler helpers (aws-lambda-powertools)
Python 3.11+ (Glue 5.x runs 3.11; Lambda supports up to 3.14).
Usage
Lambda
Create adapters at module level and run coroutines with nimble_aws.run, which keeps one event loop alive
for the whole execution environment. Warm invocations reuse the open clients.
Do not use asyncio.run per invocation: it closes the loop and the connections with it.
from nimble_aws import run
from nimble_aws.adapter import DynamoDB, S3
s3 = S3()
ddb = DynamoDB()
async def handle(event):
raw = await s3.get_object("my-bucket", event["key"], encoding="utf-8")
await ddb.put_item("my-table", {"pk": event["key"], "size": len(raw)})
return {"ok": True}
def lambda_handler(event, context):
return run(handle(event))
With the handler helpers:
from nimble_aws.handler import LambdaHandler, SQSEvent, event_source
class Handler(LambdaHandler):
@event_source(data_class=SQSEvent)
async def handle_event(self, event: SQSEvent):
failed = []
for record in event.records:
...
return SQSEvent.batch_item_failures(failed)
def generate_response(self, body):
return body
lambda_handler = Handler.entrypoint(settings)
Glue and other synchronous code
from nimble_aws import run
from nimble_aws.adapter import S3
result = run(S3().get_object_batch("bucket", keys, encoding="utf-8"))
run also works inside notebooks (Glue interactive sessions), where an event loop is already running.
ECS / long-running services
Use the adapters inside your own event loop and close the clients on shutdown:
from nimble_aws import close_clients
async def on_sigterm():
await close_clients()
Batch methods
from nimble_aws import PartialBatchError
from nimble_aws.adapter import SQS
sqs = SQS()
result = await sqs.send_message_batch(queue_url, [{"id": i} for i in range(1000)])
result.results # one entry per input, in order (None where it failed)
result.failed # [BatchFailure(index, item, code, message), ...]
try:
await sqs.send_message_batch(queue_url, messages)
except PartialBatchError as error:
retry = [f.item for f in error.result.failed]
# or decide yourself
result = await sqs.send_message_batch(queue_url, messages, raise_on_failure=False)
Dynamic parameters
Any parameter of the underlying boto3 method can be passed as a keyword argument, and anything the adapter does not wrap is one call away:
await s3.put_object("bucket", "a.json", body, ContentType="application/json", Tagging="team=data")
await ddb.query("table", {"kind": "audio"}, index="kind-index", filters={"deleted": False}, ScanIndexForward=False)
await s3.call("get_bucket_location", Bucket="bucket") # any operation
async for page in s3.paginate("list_object_versions", Bucket="bucket"):
...
Configuration
| Variable | Default | |
|---|---|---|
NIMBLE_AWS_REGION / AWS_REGION / AWS_DEFAULT_REGION / REGION |
botocore default | Region of the adapters |
NIMBLE_AWS_MAX_CONCURRENCY |
32 | Requests in flight per batch call |
NIMBLE_AWS_MAX_POOL_CONNECTIONS |
64 | HTTP connections per client |
NIMBLE_AWS_CONNECT_TIMEOUT / NIMBLE_AWS_READ_TIMEOUT |
3 s / 30 s | Lambda and Step Functions use longer read timeouts |
NIMBLE_AWS_RETRY_MODE / NIMBLE_AWS_MAX_ATTEMPTS |
standard / 3 |
botocore retries |
NIMBLE_AWS_BATCH_MAX_ATTEMPTS |
5 | Retries of unprocessed DynamoDB batch entries |
NIMBLE_AWS_SECRET_CACHE_TTL |
300 s | Secrets Manager cache (0 disables) |
Every adapter also accepts region=, config= (an AioConfig), endpoint_url=, max_concurrency= and
clients= (an AwsClients cache) in its constructor.
Adapters
| Adapter | Single | Batch |
|---|---|---|
S3 |
get_object, head_object, put_object, delete_object, list_objects, generate_presigned_url |
*_batch of each (delete_object_batch uses DeleteObjects) |
DynamoDB |
get_item, put_item, delete_item, update_item, query, scan, describe_table |
get_item_batch (BatchGetItem), put_item_batch/delete_item_batch (BatchWriteItem), update_item_batch, query_batch |
SQS |
send_message, receive_message, delete_message |
send_message_batch, receive_message_batch, delete_message_batch |
SNS |
publish |
publish_batch |
EventBridge |
put_event |
put_event_batch |
Lambda |
invoke |
invoke_batch |
StepFunctions |
start_sync_execution, start_execution |
*_batch of each |
Glue |
get_jobs, get_job_runs, start_job_run |
get_job_runs_batch, start_job_run_batch |
SecretsManager |
get_secret (cached) |
get_secret_batch (BatchGetSecretValue) |
RdsData |
execute_statement, query, transaction() |
execute_statement_batch (BatchExecuteStatement) |
ApiGateway |
post_to_connection, get_api_key |
post_to_connection_batch |
AppSync |
list_api_keys, get_api_key, get_graphql_api, get_graphql_url |
|
Cognito |
get_user, get_user_attributes |
Types (nimble_aws.pytypes)
pytypes.<service>:<Op>RequestTypeDef,<Op>OptionsTypeDef(the**kwargsof the adapter method),<Op>ResponseTypeDefand every nested shape, for each boto3 method the adapters call. Docstrings link to the boto3 reference page of the method. Regenerate after upgrading botocore:python scripts/generate_pytypes.py.pytypes.common:BatchResult,BatchFailure,ResponseMetadataTypeDef.pytypes.models:OutgoingMessage,ReceivedMessage,PutObjectEntry,ListObjectsResult,ItemUpdate,QuerySpec,EventEntry,ExecutionRequest,JobRunRequest...
Development
poetry install --with dev --extras lambda
poetry run pytest # runs against a local moto server, no AWS account needed
poetry run ruff check .
poetry run pyright
Migrating from 0.1
| 0.1 | 0.2 |
|---|---|
S3().get_files(bucket, paths, decode=True) |
(await S3().get_object_batch(bucket, keys, encoding="utf-8")).results |
put_file, put_files, put_files_with_content_type, put_files_with_metadata |
put_object(...) / put_object_batch(bucket, [{"key", "body", "ContentType": ..., "Metadata": ...}]) |
list_files(bucket, prefixes) |
list_objects_batch(bucket, prefixes, delimiter="/") (all pages) |
head_files, generate_pre_signed_url(s) |
head_object_batch, generate_presigned_url(_batch) |
DynamoDB.put_items / delete_items / get_items |
put_item_batch / delete_item_batch / get_item_batch |
get_item returned {} when missing |
returns None |
update_items(table, items, key_fields) |
update_item(table, key, attributes) / update_item_batch |
index_query(table, index, filters) |
query(table, {field: value}, index=name, filters=...) |
scan_dynamo(table) |
scan(table, segments=4) |
SQS.publish_messages_to_queue |
send_message_batch |
read_ten_messages / read_messages / read_all_messages |
receive_message / receive_message_batch(max_messages=...), returning ReceivedMessage (call .json()) |
SNS.publish_messages_to_topic(_with_attributes) |
publish_batch(topic, messages or OutgoingMessage(...)) |
EventBridge.publish_events |
put_event_batch |
StepFunctions.run_sync_execution(s) |
start_sync_execution(_batch) (raises on failed executions) |
Glue.get_jobs_runs |
get_job_runs_batch |
AppSync.list_keys / get_key / get_domain |
list_api_keys / get_api_key / get_graphql_url |
RdsData.execute_statement(cluster_arn, secret_arn, database, sql) |
RdsData(cluster_arn, secret_arn, database).execute_statement(sql, {"id": 1}) |
region= on every method |
region= on the adapter constructor |
nimble_aws.adapter.config.env |
nimble_aws.get_settings() |
License
MIT. See LICENSE.
Metadata
Release files for nimble-aws 0.2.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| nimble_aws-0.2.0.tar.gz | 53.9 kB | Without provenance | Details for nimble_aws-0.2.0.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| nimble_aws-0.2.0-py3-none-any.whl | Python 3 | none | any | Details for nimble_aws-0.2.0-py3-none-any.whl |
Total release size: 130.6 kB
Release files / nimble_aws-0.2.0.tar.gz
| Download URL | nimble_aws-0.2.0.tar.gz |
|---|---|
| Size | 53.9 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
e7fd8f5c9dd103ac446be86ec1855e6eee4661c0406989d0ca8f6c109dd8391a
|
|
BLAKE2b-256 checksum How to use checksums |
9a8f6694ca5a973fc573e7fd06071e1fdf259a5f492f2415dc9cd9eb4a4583e6
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
poetry/2.5.1 CPython/3.12.3 Linux/6.17.0-1022-azure
|
Release files / nimble_aws-0.2.0-py3-none-any.whl
| Download URL | nimble_aws-0.2.0-py3-none-any.whl |
|---|---|
| Size | 76.7 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
174af7f3ec4bd1d84d5277ea3bc5e7e34c7c91aa6ab62eb113c13f37080e33ed
|
|
BLAKE2b-256 checksum How to use checksums |
b36596cd142a7adf27a1a10c7103936d8560aa5afc10c6f338a11f2b535eeaa1
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
poetry/2.5.1 CPython/3.12.3 Linux/6.17.0-1022-azure
|
Security for nimble-aws 0.2.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.