2015-06-24 16 views
17

Devo creare un CSV e caricarlo su un bucket S3. Dal momento che sto creando il file al volo, sarebbe meglio se potessi scriverlo direttamente sul bucket S3 mentre viene creato piuttosto che scrivere l'intero file localmente e quindi caricare il file alla fine.Puoi caricare su S3 usando uno stream piuttosto che un file locale?

C'è un modo per farlo? Il mio progetto è in Python e sono abbastanza nuovo nella lingua. Ecco quello che ho provato finora:

import csv 
import csv 
import io 
import boto 
from boto.s3.key import Key 


conn = boto.connect_s3() 
bucket = conn.get_bucket('dev-vs') 
k = Key(bucket) 
k.key = 'foo/foobar' 

fieldnames = ['first_name', 'last_name'] 
writer = csv.DictWriter(io.StringIO(), fieldnames=fieldnames) 
k.set_contents_from_stream(writer.writeheader()) 

ho ricevuto questo errore: BotoClientError: s3 non supporta il trasferimento Chunked

UPDATE: ho trovato un modo di scrivere direttamente a S3, ma non posso trovare un modo per cancellare il buffer senza effettivamente eliminare le righe che ho già scritto. Così, per esempio:

conn = boto.connect_s3() 
bucket = conn.get_bucket('dev-vs') 
k = Key(bucket) 
k.key = 'foo/foobar' 

testDict = [{ 
    "fieldA": "8", 
    "fieldB": None, 
    "fieldC": "888888888888"}, 
    { 
    "fieldA": "9", 
    "fieldB": None, 
    "fieldC": "99999999999"}] 

f = io.StringIO() 
fieldnames = ['fieldA', 'fieldB', 'fieldC'] 
writer = csv.DictWriter(f, fieldnames=fieldnames) 
writer.writeheader() 
k.set_contents_from_string(f.getvalue()) 

for row in testDict: 
    writer.writerow(row) 
    k.set_contents_from_string(f.getvalue()) 

f.close() 

Scrive 3 righe al file, ma io sono in grado di liberare la memoria per scrivere un file di grandi dimensioni. Se aggiungo:

f.seek(0) 
f.truncate(0) 

al ciclo, quindi viene scritta solo l'ultima riga del file. C'è un modo per rilasciare le risorse senza eliminare le righe dal file?

+0

Anche se si potrebbe scrivere a S3 come vuoi tu, io non lo consiglio a causa di problemi di coerenza . Perché pensi che sarebbe meglio non scrivere localmente? Volete un oggetto S3 parziale se c'è stata un'eccezione o un problema? Presumo di no. – cgseller

+1

Stavo cercando di scrivere direttamente per essere un po 'più efficiente. In sostanza se scrivo il file localmente e lo carico, sto aggiungendo il caricamento come passaggio aggiuntivo e pulendo il file locale. Non mi interessa avere un file incompleto - potrei avere un file incompleto se lo scrivo anche a livello locale. Il sistema sarà idempotente e cancellerà un file in uno stato di errore o lo proseguirà. –

risposta

16

Ho trovato una soluzione alla mia domanda, che posterò qui nel caso in cui qualcun altro sia interessato. Ho deciso di farlo come parti in un caricamento multipart. Non puoi eseguire lo streaming su S3. C'è anche un pacchetto disponibile che cambia il tuo file di streaming su un caricamento multipart che ho usato: Smart Open.

import smart_open 
import io 
import csv 

testDict = [{ 
    "fieldA": "8", 
    "fieldB": None, 
    "fieldC": "888888888888"}, 
    { 
    "fieldA": "9", 
    "fieldB": None, 
    "fieldC": "99999999999"}] 

fieldnames = ['fieldA', 'fieldB', 'fieldC'] 
f = io.StringIO() 
with smart_open.smart_open('s3://dev-test/bar/foo.csv', 'wb') as fout: 
    writer = csv.DictWriter(f, fieldnames=fieldnames) 
    writer.writeheader() 
    fout.write(f.getvalue()) 

    for row in testDict: 
     f.seek(0) 
     f.truncate(0) 
     writer.writerow(row) 
     fout.write(f.getvalue()) 

f.close() 
+0

Per Python 2, assicurati di usare 'StringIO.StringIO()' invece di 'io.StringIO()', altrimenti riceverai un errore di codifica – Anconia

5

Secondo docs è possibile

s3.Object('mybucket', 'hello.txt').put(Body=open('/tmp/hello.txt', 'rb')) 

modo che possiamo utilizzare StringIO in modo ordinario

+1

Non capisco come usarlo. /tmp/hello.txt non è un file locale, che cosa stiamo cercando di evitare? – EthanP

+0

@EthanP [StringIO] (https://docs.python.org/2/library/stringio.html) - Legge e scrive stringhe come file. Utilizzare l'oggetto 'StringIO' invece del file –

+0

No, in base a [questo ticket] (https://github.com/boto/boto3/issues/256), non è supportato. L'idea di utilizzare i flussi con S3 è di evitare l'uso di file statici quando necessario per caricare file enormi di alcuni gigabyte. Sto cercando di risolvere anche questo problema - ho bisogno di leggere un grande dato da mongodb e di metterlo in S3, non voglio usare i file. – baldr