terricain / aioboto3

Wrapper to use boto3 resources with the aiobotocore async backend
Apache License 2.0
719 stars 74 forks source link

There is no `copy_from` method for the `Object` object of `s3` resource #251

Closed bubanoid closed 2 years ago

bubanoid commented 2 years ago

details:

aioboto3==9.3.0
aiobotocore==2.0.1
boto3==1.19.8
botocore==1.22.8

details:

Python 3.9.6

details:

macOS Monterrey
Version 12.1 (21C52)

Description

There is no copy_from method for the Object object of s3 resource

What I Did

import asyncio
import aioboto3

BUCKET = "test"
PATH = "path.json"
PATH_NEW = "path_{}.json"

async def async_range(count):
    for i in range(count):
        yield(i)

async def main():
    session = aioboto3.session.Session(profile_name="AWSResearch")
    async with session.resource('s3') as resource:
        async for i in async_range(100):
            await resource.Object(BUCKET, PATH_NEW.format(i)).copy_from(CopySource=f"{BUCKET}/{PATH}")

asyncio.run(main())

raises an error:

AttributeError: 'coroutine' object has no attribute 'copy_from'

however the synchronous version of this script works without errors.

It would be great if you add this method to the library.

terricain commented 2 years ago

oooh, yeah this most likely needs to be patched in, will have a look into it

terricain commented 2 years ago

I just added this test which worked fine

@pytest.mark.asyncio
async def test_s3_copy_from(event_loop, s3_client, s3_resource, bucket_name):
    data = b'Hello World\n'
    await s3_client.create_bucket(Bucket=bucket_name)

    fh = BytesIO()
    fh.write(data)
    fh.seek(0)

    await s3_client.upload_fileobj(fh, bucket_name, 'test_file')

    resource = await s3_resource.Object(bucket_name, "new_test_file")
    copy_source = bucket_name + "/test_file"
    copy_result = await resource.copy_from(CopySource=copy_source)
    assert 'CopyObjectResult' in copy_result

    resp = await s3_client.get_object(Bucket=bucket_name, Key='new_test_file')
    assert (await resp['Body'].read()) == data

I suspect its unhappy as both resource.Object() AND .copy_from each return a coroutine, so you'll want to await resource.Object into a variable then await variable.copy_from

bubanoid commented 2 years ago

Yeah, you are right. Intermediate variable res = await resource.Object(BUCKET, PATH_NEW.format(i)) solves the problem. Thank you!