web-dev-qa-db-fra.com

Comment utiliser boto pour diffuser un fichier hors Amazon S3 vers Rackspace Cloudfiles?

Je copie un fichier de S3 vers Cloudfiles et j'aimerais éviter de l'écrire sur le disque. La bibliothèque Python-Cloudfiles a un appel object.stream () qui ressemble à ce dont j'ai besoin, mais je ne trouve pas d'appel équivalent dans boto. J'espère pouvoir faire quelque chose comme:

shutil.copyfileobj(s3Object.stream(),rsObject.stream())

Est-ce possible avec boto (ou toute autre bibliothèque s3, je suppose)?

23
joemastersemison

L'objet Key dans boto, qui représente un objet dans S3, peut être utilisé comme un itérateur. Vous devriez donc pouvoir faire quelque chose comme ceci:

>>> import boto
>>> c = boto.connect_s3()
>>> bucket = c.lookup('garnaat_pub')
>>> key = bucket.lookup('Scan1.jpg')
>>> for bytes in key:
...   write bytes to output stream

Ou, comme dans le cas de votre exemple, vous pourriez faire:

>>> shutil.copyfileobj(key, rsObject.stream())
17
garnaat

D'autres réponses dans ce fil sont liées à boto, mais S3.Object n'est plus itérable dans boto3. Donc, ce qui suit NE FONCTIONNE PAS, il génère un message d'erreur TypeError: 's3.Object' object is not iterable:

    s3 = boto3.session.Session(profile_name=my_profile).resource('s3')
    s3_obj = s3.Object(bucket_name=my_bucket, key=my_key)

    with io.FileIO('sample.txt', 'w') as file:
        for i in s3_obj:
            file.write(i)

Dans boto3, le contenu de l'objet est disponible à S3.Object.get()['Body'] qui n'est pas non plus itérable, donc ce qui suit NE FONCTIONNE PAS:

    body = s3_obj.get()['Body']
    with io.FileIO('sample.txt', 'w') as file:
        for i in body:
            file.write(i)

Une alternative consiste donc à utiliser la méthode de lecture, mais cela charge en mémoire l'objet WHOLE S3 qui, lorsqu'il s'agit de gros fichiers, n'est pas toujours une possibilité:

    body = s3_obj.get()['Body']
    with io.FileIO('sample.txt', 'w') as file:
        for i in body.read():
            file.write(i)

Mais la méthode read permet de transmettre le paramètre amt en spécifiant le nombre d'octets que nous voulons lire à partir du flux sous-jacent. Cette méthode peut être appelée à plusieurs reprises jusqu'à ce que tout le flux ait été lu:

    body = s3_obj.get()['Body']
    with io.FileIO('sample.txt', 'w') as file:
        while file.write(body.read(amt=512)):
            pass

En creusant dans le code botocore.response.StreamingBody, on s'aperçoit que le flux sous-jacent est également disponible. On peut donc l'itérer comme suit:

    body = s3_obj.get()['Body']
    with io.FileIO('sample.txt', 'w') as file:
        for b in body._raw_stream:
            file.write(b)

Pendant la recherche sur Google, j'ai également vu des liens qui pourraient être utilisés, mais je n'ai pas essayé:

36
smallo

Je pense qu'au moins certaines des personnes qui verront cette question seront comme moi et voudront un moyen de diffuser un fichier ligne par ligne (ou virgule par virgule, ou tout autre délimiteur). Voici un moyen simple de le faire:

def getS3ResultsAsIterator(self, aws_access_info, key, prefix):        
    s3_conn = S3Connection(**aws_access)
    bucket_obj = s3_conn.get_bucket(key)
    # go through the list of files in the key
    for f in bucket_obj.list(prefix=prefix):
        unfinished_line = ''
        for byte in f:
            byte = unfinished_line + byte
            #split on whatever, or use a regex with re.split()
            lines = byte.split('\n')
            unfinished_line = lines.pop()
            for line in lines:
                yield line

La réponse de @ garnaat ci-dessus est toujours excellente et vraie à 100%. Espérons que le mien aide toujours quelqu'un.

20
Eli

Ceci est ma solution d'habillage du corps en streaming:

import io
class S3ObjectInterator(io.RawIOBase):
    def __init__(self, bucket, key):
        """Initialize with S3 bucket and key names"""
        self.s3c = boto3.client('s3')
        self.obj_stream = self.s3c.get_object(Bucket=bucket, Key=key)['Body']

    def read(self, n=-1):
        """Read from the stream"""
        return self.obj_stream.read() if n == -1 else self.obj_stream.read(n)

Exemple d'utilisation:

obj_stream = S3ObjectInterator(bucket, key)
for line in obj_stream:
    print line
2
jzhou

La variable StreamingBody de Botocore a une méthode iter_lines():

https://botocore.amazonaws.com/v1/documentation/api/latest/reference/response.html#botocore.response.StreamingBody.iter_lines

Alors:

import boto3
s3r = boto3.resource('s3')
iterator = s3r.Object(bucket, key).get()['Body'].iter_lines()

for line in iterator:
    print(line)
1
Vic