Seite 1 von 1

Bytestream komprimieren und tar anhängen

Verfasst: Freitag 18. Mai 2018, 09:51
von sls
Hallo,

ich habe einen Mikroservice gebaut, der via HTTP (POST) verschieden große Daten entgegen nimmt (Max. 100 MB). Diese Daten werden in der Applikation mit einer ID versehen, und anschließend mittels gzip komprimiert und einem stinknormalen Tararchiv angefügt, das bei `with gzip.open('file.gz', mode='wb')` entstehende, temporär erstellte File wird anschließend von der Platte gelöscht. Das Tar selbst ist nicht komprimiert, und beinhaltet somit n-verschiedene .gz-Dateien. An diese Vorgabe muss ich mich aufgrund anderer Services die auf diese Archive zugreifen, halten.

Nun dachte ich mir, dass man diesen ganzen Vorgang evtl. abkürzen kann, in dem man den Datenstrom entgegen nimmt, komprimiert, und dem Tararchiv anfügt, ohne eine temporäre Datei auf Platte schreiben und anschließend löschen zu müssen. Leider komme ich mit meinem Versuch nicht weiter:

Code: Alles auswählen

def append_to_tar(data):

    buffer = io.BytesIO()
    buffer.write(data)
    buffer.seek(0)

    with gzip.GzipFile(fileobj=buffer, mode='wb') as compressed_data_stream:
        with tarfile.open('foo.tar', mode='a') as fd:
            tarinfo = tarfile.TarInfo(name="bar.gz")
            tarinfo.size = len(buffer.getbuffer())
            fd.addfile(tarinfo=tarinfo, fileobj=compressed_data_stream)


if __name__ == '__main__':
    sample_byte_stream = b"Hallo bla, bla blabla bla."
    append_to_tar(sample_byte_stream)

Code: Alles auswählen

Traceback (most recent call last):
  File "/app/test.py", line 48, in <module>
    append_to_tar(sample_byte_stream)
  File "app/test.py", line 43, in append_to_tar
    fd.addfile(tarinfo=tarinfo, fileobj=compressed_data_stream)
  File "/usr/lib/python3.6/tarfile.py", line 1977, in addfile
    copyfileobj(fileobj, self.fileobj, tarinfo.size, bufsize=bufsize)
  File "/usr/lib/python3.6/tarfile.py", line 254, in copyfileobj
    buf = src.read(remainder)
  File "/usr/lib/python3.6/gzip.py", line 275, in read
    raise OSError(errno.EBADF, "read() on write-only GzipFile object")
OSError: [Errno 9] read() on write-only GzipFile object

Process finished with exit code 1
Die Fehlermeldung scheint erstmal plausibel, nur habe ich keine funktionierende Lösung wie ich den komprimierten Stream als Fileobjekt kopieren, in 'rb' öffnen und anschließend dem Tar anfügen kann. Ist das überhaupt irgendwie sinnvoll / möglich den Stream direkt in das Tar zu schubsen?

Re: Bytestream komprimieren und tar anhängen

Verfasst: Freitag 18. Mai 2018, 10:11
von __deets__
Ich denke dein Problem ist einfach nur das die BytesIO Instanz Write only ist. Dafür gibt es erstmal keinen technischen Grund, denn Speicher ist natürlich lesbar. Ein einfacher fix wäre also den Inhalt des bytesio Objektes in ein neues zu packen, das readable ist. Und ggf kann man auch einen mode angeben beim ersten, der “rw” ist, und damit deine gewünschte Funktionalität erlaubt.

Re: Bytestream komprimieren und tar anhängen

Verfasst: Freitag 18. Mai 2018, 10:47
von DasIch
Hier ist was du machst:

1. Du erstellst buffer, ein Datei-ähnliches Objekt mit den Daten die du komprimieren willst.
2. Du erstellt ein GzipFile in wb und übergibst den buffer. Falls du in GzipFile schreiben würdest, würden die Daten die du schreibst komprimiert im buffer gespeichert statt dessen tust du damit nichts.
3. Du übergibst dein GzipFile an TarFile.add was dieses versucht einzulesen, was nicht klappt zum einen weil dein GzipFile nur zum schreiben geöffnet ist, wäre es zum lesen geöffnet würde GzipFile sich beschweren weil es die Daten im buffer nicht dekomprimieren kann.

Re: Bytestream komprimieren und tar anhängen

Verfasst: Freitag 18. Mai 2018, 10:56
von Sirius3
Hier, was Du machen solltest:

1. Die Daten nehmen und komprimiert in den buffer schreiben.
2. Aus dem Buffer lesen und ins tar schreiben.

Code: Alles auswählen

def append_to_tar(data):
    buffer = io.BytesIO()
    with gzip.GzipFile(fileobj=buffer, mode='wb') as compressed_data_stream:
        compressed_data_stream.write(data)
    buffer.seek(0)

    with tarfile.open('foo.tar', mode='a') as fd:
        tarinfo = tarfile.TarInfo(name="bar.gz")
        tarinfo.size = len(buffer.getbuffer())
        fd.addfile(tarinfo=tarinfo, fileobj=buffer)

Re: Bytestream komprimieren und tar anhängen

Verfasst: Freitag 18. Mai 2018, 12:06
von sls
Alles klar, vielen Dank für eure Hilfe, mir ist gerade auch noch aufgefallen, dass ich das `with`-statement falsch verwendet habe, da, sofern ich überhaupt etwas ins gzip-Objekt geschrieben hätte, der Stream nicht sauber geschrieben worden wäre. Die .gz-Files hätten spätestens mit gunzip nicht dekomprimiert werden können.

Jetzt funktioniert's einwandrei. Das freut mich sehr !