{"id":15440876,"url":"https://github.com/samuelcolvin/aioaws","last_synced_at":"2025-06-22T06:02:27.687Z","repository":{"id":38962103,"uuid":"250011615","full_name":"samuelcolvin/aioaws","owner":"samuelcolvin","description":"Asyncio compatible SDK for aws services.","archived":false,"fork":false,"pushed_at":"2024-11-18T21:32:37.000Z","size":107,"stargazers_count":175,"open_issues_count":7,"forks_count":14,"subscribers_count":6,"default_branch":"main","last_synced_at":"2025-05-23T01:07:39.526Z","etag":null,"topics":["asyncio","aws","python","python38","python39","s3","ses"],"latest_commit_sha":null,"homepage":"https://pypi.org/project/aioaws/","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/samuelcolvin.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2020-03-25T15:09:16.000Z","updated_at":"2025-05-08T13:04:04.000Z","dependencies_parsed_at":"2024-12-07T17:02:03.651Z","dependency_job_id":"093a3576-5171-433e-8cde-06ad8b468026","html_url":"https://github.com/samuelcolvin/aioaws","commit_stats":{"total_commits":74,"total_committers":5,"mean_commits":14.8,"dds":"0.17567567567567566","last_synced_commit":"d4141e30a11c2724558c330631de20ea02691f3d"},"previous_names":[],"tags_count":14,"template":false,"template_full_name":null,"purl":"pkg:github/samuelcolvin/aioaws","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/samuelcolvin%2Faioaws","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/samuelcolvin%2Faioaws/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/samuelcolvin%2Faioaws/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/samuelcolvin%2Faioaws/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/samuelcolvin","download_url":"https://codeload.github.com/samuelcolvin/aioaws/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/samuelcolvin%2Faioaws/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":261243971,"owners_count":23129633,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["asyncio","aws","python","python38","python39","s3","ses"],"created_at":"2024-10-01T19:15:41.774Z","updated_at":"2025-06-22T06:02:22.680Z","avatar_url":"https://github.com/samuelcolvin.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# aioaws\n\n[![CI](https://github.com/samuelcolvin/aioaws/actions/workflows/ci.yml/badge.svg?event=push)](https://github.com/samuelcolvin/aioaws/actions?query=event%3Apush+branch%3Amain+workflow%3ACI)\n[![Coverage](https://codecov.io/gh/samuelcolvin/aioaws/branch/main/graph/badge.svg)](https://codecov.io/gh/samuelcolvin/aioaws)\n[![pypi](https://img.shields.io/pypi/v/aioaws.svg)](https://pypi.python.org/pypi/aioaws)\n[![versions](https://img.shields.io/pypi/pyversions/aioaws.svg)](https://github.com/samuelcolvin/aioaws)\n[![license](https://img.shields.io/github/license/samuelcolvin/aioaws.svg)](https://github.com/samuelcolvin/aioaws/blob/main/LICENSE)\n\nAsyncio SDK for some AWS services.\n\nThis library does not depend on boto, boto3 or any of the other bloated, opaque and mind thumbing AWS SDKs. Instead, it\nis written from scratch to provide clean, secure and easily debuggable access to AWS services I want to use.\n\nThe library is formatted with black and includes complete type hints (mypy passes in strict-mode).\n\nIt currently supports:\n* **S3** - list, delete, recursive delete, generating signed upload URLs, generating signed download URLs\n* **SES** - sending emails including with attachments and multipart\n* **SNS** - enough to get notifications about mail delivery from SES\n* [AWS Signature Version 4](https://docs.aws.amazon.com/AmazonS3/latest/API/sigv4-auth-using-authorization-header.html)\n  authentication for any AWS service (this is the only clean \u0026 modern implementation of AWS4 I know of in python, see\n  [`core.py`](https://github.com/samuelcolvin/aioaws/blob/main/aioaws/core.py#L120-L175))\n\nThe only dependencies of **aioaws**, are:\n* **aiofiles** - for asynchronous reading of files\n* **cryptography** - for verifying SNS signatures\n* **httpx** - for HTTP requests\n* **pydantic** - for validating responses\n\n## Install\n\n```bash\npip install aioaws\n```\n\n## S3 Usage\n\n\n```py\nimport asyncio\nfrom aioaws.s3 import S3Client, S3Config\nfrom httpx import AsyncClient\n\n# requires `pip install devtools`\nfrom devtools import debug\n\nasync def s3_demo(client: AsyncClient):\n    s3 = S3Client(client, S3Config('\u003caccess key\u003e', '\u003csecret key\u003e', '\u003cregion\u003e', 'my_bucket_name.com'))\n\n    # upload a file:\n    await s3.upload('path/to/upload-to.txt', b'this the content')\n\n    # list all files in a bucket\n    files = [f async for f in s3.list()]\n    debug(files)\n    \"\"\"\n    [\n        S3File(\n            key='path/to/upload-to.txt',\n            last_modified=datetime.datetime(...),\n            size=16,\n            e_tag='...',\n            storage_class='STANDARD',\n        ),\n    ]\n    \"\"\"\n    # list all files with a given prefix in a bucket\n    files = [f async for f in s3.list('path/to/')]\n    debug(files)\n\n    # # delete a file\n    # await s3.delete('path/to/file.txt')\n    # # delete two files\n    # await s3.delete('path/to/file1.txt', 'path/to/file2.txt')\n    # delete recursively based on a prefix\n    await s3.delete_recursive('path/to/')\n\n    # generate an upload link suitable for sending to a browser to enabled\n    # secure direct file upload (see below)\n    upload_data = s3.signed_upload_url(\n        path='path/to/',\n        filename='demo.png',\n        content_type='image/png',\n        size=123,\n    )\n    debug(upload_data)\n    \"\"\"\n    {\n        'url': 'https://my_bucket_name.com/',\n        'fields': {\n            'Key': 'path/to/demo.png',\n            'Content-Type': 'image/png',\n            'AWSAccessKeyId': '\u003caccess key\u003e',\n            'Content-Disposition': 'attachment; filename=\"demo.png\"',\n            'Policy': '...',\n            'Signature': '...',\n        },\n    }\n    \"\"\"\n\n    # generate a temporary link to allow yourself or a client to download a file\n    download_url = s3.signed_download_url('path/to/demo.png', max_age=60)\n    print(download_url)\n    #\u003e https://my_bucket_name.com/path/to/demo.png?....\n\nasync def main():\n    async with AsyncClient(timeout=30) as client:\n        await s3_demo(client)\n\nasyncio.run(main())\n```\n\n`upload_data` shown in the above example can be used in JS with something like this:\n\n```js\nconst formData = new FormData()\nfor (let [name, value] of Object.entries(upload_data.fields)) {\n  formData.append(name, value)\n}\nconst fileField = document.querySelector('input[type=\"file\"]')\nformData.append('file', fileField.files[0])\n\nconst response = await fetch(upload_data.url, {method: 'POST', body: formData})\n```\n\n(in the request to get `upload_data` you would need to provide the file size and content-type in order\nfor them for the upload shown here to succeed)\n\n\n## SES\n\nTo send an email with SES:\n\n```py\nimport asyncio\nfrom pathlib import Path\nfrom httpx import AsyncClient\nfrom aioaws.ses import SesConfig, SesClient, SesRecipient, SesAttachment\n\nasync def ses_demo(client: AsyncClient):\n    ses_client = SesClient(client, SesConfig('\u003caccess key\u003e', '\u003csecret key\u003e', '\u003cregion\u003e'))\n\n    message_id = await ses_client.send_email(\n        SesRecipient('sende@example.com', 'Sender', 'Name'),\n        'This is the subject',\n        [SesRecipient('recipient@eample.com', 'John', 'Doe')],\n        'this is the plain text body',\n        html_body='\u003cb\u003eThis is the HTML body.\u003cb\u003e',\n        bcc=[SesRecipient(...)],\n        attachments=[\n            SesAttachment(b'this is content', 'attachment-name.txt', 'text/plain'),\n            SesAttachment(Path('foobar.png')),\n        ],\n        unsubscribe_link='https:://example.com/unsubscribe',\n        configuration_set='SES configuration set',\n        message_tags={'ses': 'tags', 'go': 'here'},\n    )\n    print('SES message ID:', message_id)\n\nasync def main():\n    async with AsyncClient() as client:\n        await ses_demo(client)\n\nasyncio.run(main())\n```\n\n## SNS\n\nReceiving data about SES webhooks from SNS (assuming you're using FastAPI)\n\n```py\nfrom aioaws.ses import SesWebhookInfo\nfrom aioaws.sns import SnsWebhookError\nfrom fastapi import Request\nfrom httpx import AsyncClient\n\nasync_client = AsyncClient...\n\n@app.post('/ses-webhook/', include_in_schema=False)\nasync def ses_webhook(request: Request):\n    request_body = await request.body()\n    try:\n        webhook_info = await SesWebhookInfo.build(request_body, async_client)\n    except SnsWebhookError as e:\n        debug(message=e.message, details=e.details, headers=e.headers)\n        raise ...\n\n    debug(webhook_info)\n    ...\n```\n\nSee [here](https://github.com/samuelcolvin/aioaws/blob/main/aioaws/ses.py#L196-L204)\nfor more information about what's provided in a `SesWebhookInfo`.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsamuelcolvin%2Faioaws","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsamuelcolvin%2Faioaws","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsamuelcolvin%2Faioaws/lists"}