Download von Dateien

Django, Flask, Bottle, WSGI, CGI…
Sternenregen
User
Beiträge: 39
Registriert: Mittwoch 13. Januar 2021, 16:17

Sobald der User auf der HTML Seite auf einen Button klickt, soll ein csv gedownloadet werden können. Das CSV wird aus einem Dataframe erstellt.

Ich habe bereits ein minimal Exempel auf meinem privaten Rechner erstellt und es funktioniert.

Mache ich haargenau das gleiche auf dem Arbeitsrechner, wird ein "RecursionsError" geworfen und dass ich die maximale Anzahl an Rekursiven erreicht habe.

Der einzige Unterschied ist, dass natürlich in dem Datensatz auf dem Arbeitsrechner wesentlich mehr Daten sind.
Ich bin mir zudem unschlüssig, wo genau das Problem liegt.

Ich habe auch versucht das Format der Datei zu ändern. (HTML und JSON), aber dann passiert irgendwie
überhaupt nichts. Kein Fehler, nichts. Aber der Download wird nicht gestartet.

Hier ist mein Code:

Code: Alles auswählen

@application.route(/....)
def download_file():
      buff_bytes_csv = download_result()

      return send_file(buffered_bytes_csv, mimetype="text/csv", as_attachment=True, attachment_filename="result.csv")

Code: Alles auswählen

def download_result():
    ....
    csv_file = df.to_csv()
    buffered_str = io.StringIO(csv_file)
    buffered_bytes = io.BytesIO(buffered_str.read().enconde("utf-8"))

    return buffered_bytes
nezzcarth
User
Beiträge: 1751
Registriert: Samstag 16. April 2011, 12:47

Bitte zeig doch den gesamten Traceback, den du bekommst. Ohne kann man das kaum beantworten.
Sternenregen
User
Beiträge: 39
Registriert: Mittwoch 13. Januar 2021, 16:17

Ich wollte den Fehler gerade reproduzieren und nun taucht er nicht mehr auf -.- Es passiert aber trotzdem kein Download.

Anhand meiner Google Recherche kann ich dir aber den relevanten Tracebackteil aus meiner Googlesuche zeigen:

Code: Alles auswählen

recursionerror: maximum recursion depth exceeded
Entweder das hilft weiter, oder ich muss nach Alternativen schauen bzw gucken ob der Fehler erneut auftritt.

Ich möchte aber noch erwähnen, dass die Anwendung in einem Dockercontainer läuft. Vielleicht hilft diese Information auch weiter.
Sirius3
User
Beiträge: 18258
Registriert: Sonntag 21. Oktober 2012, 17:20

Das ist eben nicht der relevante Teil des Tracebacks. In dem von Dir gezeigten Codefragment kommt keine Rekursion vor, dort liegt also nicht der Fehler. Mit Traceback sind die vielen Zeilen gemeint, in denen die Zeilen stehen,in denen der Fehler auftaucht und dazu brauchen wir auch den relevanten Code. Sonst können wir das Problem nicht verstehen und auch nicht helfen.
rogerb
User
Beiträge: 878
Registriert: Dienstag 26. November 2019, 23:24

Selbst dieses Codefragment enthält schon zwei Fehler, die unabhängig vom geschilderten Problem auftreten.

mal heißt es "buff_bytes_csv ", dann soll aber "buffered_bytes_csv" gesendet werden.
enconde("utf-8") ist falsch. Richtig wäre: encode("utf-8")

Aber das ist wohl nicht aufgefallen, weil es vorher schon in die Hose geht.
Benutzeravatar
__blackjack__
User
Beiträge: 14016
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

Der Umweg über `StringIO` ist auch irgendwie total überflüssig.
“The best book on programming for the layman is »Alice in Wonderland«; but that's because it's the best book on anything for the layman.” — Alan J. Perlis
Sternenregen
User
Beiträge: 39
Registriert: Mittwoch 13. Januar 2021, 16:17

rogerb hat geschrieben: Sonntag 5. September 2021, 11:04 Selbst dieses Codefragment enthält schon zwei Fehler, die unabhängig vom geschilderten Problem auftreten.

mal heißt es "buff_bytes_csv ", dann soll aber "buffered_bytes_csv" gesendet werden.
enconde("utf-8") ist falsch. Richtig wäre: encode("utf-8")

Aber das ist wohl nicht aufgefallen, weil es vorher schon in die Hose geht.
Das hast du super erkannt, Sherlock.

__blackjack__ hat geschrieben: Sonntag 5. September 2021, 12:46 Der Umweg über `StringIO` ist auch irgendwie total überflüssig.
Wenn du dir schon die Mühe machst zu Antworten, dann begründe es doch auch. Dann kann ich vielleicht noch was lernen. Ansonsten ist eigentlich nur dein Post überflüssig.



Wie gesagt. ich versuche den Fehler zu reproduzieren.
Benutzeravatar
sparrow
User
Beiträge: 4535
Registriert: Freitag 17. April 2009, 10:28

@Sternemregen: Umgekehrt wird ein Schuh draus. StringIO an der Stelle zu verwenden macht überhaupt gar keinen Sinn. Was erhoffst du dir davon? Welchen Vorteil siehst du darin, die Zeichenkette in das File-Like-Object zu stecken, dann wieder herauszuholen und dann encode darauf aufzurufen?

Ich sehe übrigens nicht, warum es hier einen Grund für dich gibt patzig zu werden. Wenn du hier fehlerhaften Code einstellst, dann musst du damit rechnen, dass dir Menschen sagten,d ass er fehlerhaft ist. Du hättest ja auch einfach den richtigen, lauffähigen Code nehmen sollen, statt dir offensichtlich fehlerhaften auszudenken.
Benutzeravatar
__blackjack__
User
Beiträge: 14016
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Sternenregen: Ich dachte das wäre offensichtlich. Da wird eine Zeichenkette in ein `StringIO`-Objekt geschrieben, und das wird dann wieder ausgelesen. Was zu der gleichen Zeichenkette führt die man da rein geschrieben hat. Das wäre als würde man bei ganzen Zahlen ``b = a * 1`` oder ``b = a + 0`` schreiben. Kann man machen, ist aber nicht sinnvoll, weil es nichts am Wert von `a` ändert, und man den auch gleich zum weiterrechnen verwenden könnte. ``b = io.StringIO(a).read()`` ist letztlich das gleiche. Danach gilt immer ``b == a``. Man könnte auch ``b = a`` schreiben, und da stellt sich dann die Frage wofür man `b` braucht.
“The best book on programming for the layman is »Alice in Wonderland«; but that's because it's the best book on anything for the layman.” — Alan J. Perlis
Benutzeravatar
noisefloor
User
Beiträge: 4185
Registriert: Mittwoch 17. Oktober 2007, 21:40
Wohnort: WW
Kontaktdaten:

Hallo,
Ich habe bereits ein minimal Exempel auf meinem privaten Rechner erstellt und es funktioniert.
Ich orakle mal - bedingt durch fehlenden Code - dass genau das der Ausgangspunkt ist. Python wirft den Rekursionsfehler, wenn die Rekursionstiefe 1000 (sofern du den Wert nicht geändert hast) überschreitet. Da kommst du mit wenigen Testdaten wahrscheinlich nicht hin, mit realen Datenmengen dann vielleicht schon?

Generell ist Rekursion _nicht_ das Mittel der Wahl in Python, weil Python null Optimierung für Rekursion hat. Und wenn du, wie der Ausgangspost vermuten lässt, Pandas benutzt, dann ist der Einsatz von Rekursion extrem unüblich.

Gruß, noisefloor
Sternenregen
User
Beiträge: 39
Registriert: Mittwoch 13. Januar 2021, 16:17

Guten Abend noisefloor,

vielen Dank für deinen Post. In der tat nutze ich Pandas um ein CSV-File zu erstellen. Dieses möchte ich dann downloaden können. Den Rekursionsfehler erhalte ich mittlerweile nicht mehr. Warum kann ich leider nicht beantworten.

UPDATE:
Mittlerweile bin ich in meinen Recherchen weiter gekommen. Ich habe jetzt auch mein Minimal-Beispiel in ein Docker Container gepackt. Es funktioniert alles einwandfrei. Der Code funktioniert. Der einzige Unterschied zu der Produktivumgebung ist die Datenmenge.

Möchte ich der tatsächlichen Anwendung etwas downloaden, passiert nichts. Weder im Browser, noch in den Docker-Logs noch sonst irgendwo ist nur der Hauch eines Fehlers zu erkennen. Es passiert einfach nichts. Kein File oder sonst irgendetwas was auf mit einem Download zu tun hätte. Ich habe auch mittlerweile viele Sachen probiert die ich im Netz gefunden habe, bisher leider ohne Erfolg. Eventuell ändere ich mal das Format des Files.
Benutzeravatar
__blackjack__
User
Beiträge: 14016
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Sternenregen: Vielleicht dauert es einfach nur sehr lange und/oder der Speicher reicht nicht aus. Das Vorgehen ist nicht wirklich speicherschonend. Alleine der Code den wir sehen hat das ganze Ergebnis als DataFrame und als Zeichenkette und als Bytes-Objekt gleichzeitig im Speicher. Und einen grossen DataFrame dynamisch aufzubauen ist auch nicht so toll, und wenn da auch alles an Namen gebunden wird, gibt es da das komplette Ergebnis vielleicht *noch mal* komplett im Speicher.

Ich würde mir mal den Speicherverbrauch auf dem Produktivsystem anschauen.
“The best book on programming for the layman is »Alice in Wonderland«; but that's because it's the best book on anything for the layman.” — Alan J. Perlis
Sternenregen
User
Beiträge: 39
Registriert: Mittwoch 13. Januar 2021, 16:17

__blackjack__ hat geschrieben: Sonntag 5. September 2021, 20:48 @Sternenregen: Vielleicht dauert es einfach nur sehr lange und/oder der Speicher reicht nicht aus. Das Vorgehen ist nicht wirklich speicherschonend. Alleine der Code den wir sehen hat das ganze Ergebnis als DataFrame und als Zeichenkette und als Bytes-Objekt gleichzeitig im Speicher. Und einen grossen DataFrame dynamisch aufzubauen ist auch nicht so toll, und wenn da auch alles an Namen gebunden wird, gibt es da das komplette Ergebnis vielleicht *noch mal* komplett im Speicher.

Ich würde mir mal den Speicherverbrauch auf dem Produktivsystem anschauen.
Vielen Dank für den Hinweis. Dem werde ich auf jeden Fall gleich mal auf dem Grund gehen.
Sternenregen
User
Beiträge: 39
Registriert: Mittwoch 13. Januar 2021, 16:17

So, das erste Test Exampel war auf einem Mac. Das zweite Test Exampel habe ich jetzt auf einer Windows Maschine erstellt.

Gleicher Code. Aber jetzt wird tatsächlich eine Fehlermeldung geworfen:

Code: Alles auswählen

File "C:\Users\user\PycharmProjects\ex2\venv\lib\site-packages\flask\app.py", line 1513, in full_dispatch_request
    rv = self.dispatch_request()
  File "C:\Users\user\PycharmProjects\ex2\venv\lib\site-packages\flask\app.py", line 1499, in dispatch_request
    return self.ensure_sync(self.view_functions[rule.endpoint])(**req.view_args)
  File "C:\Users\user\PycharmProjects\ex2\main.py", line 143, in save_cluster_result
    return send_file(csv_file,
  File "C:\Users\user\PycharmProjects\ex2\venv\lib\site-packages\flask\helpers.py", line 612, in send_file
    return werkzeug.utils.send_file(
  File "C:\Users\user\PycharmProjects\ex2\venv\lib\site-packages\werkzeug\utils.py", line 697, in send_file
    stat = os.stat(path)
OSError: [WinError 123] Die Syntax für den Dateinamen, Verzeichnisnamen oder die Datenträgerbezeichnung ist falsch: 'C:\\Users\\user\\PycharmProjects\\ex2\\,0\r\ntest1,123\r\ntest2,123\r\n
Ich schätze mal die Fehlermeldung beruht auf der SPeicheradresse des Dataframes. Denn ich habe in dem Test Exampel keinerlei Pfadangaben. Das Dataframe wird on the fly erzeugt. Generiert wird dieses aus einem Dictionary mit 2 key/Value Einträgen.
Benutzeravatar
noisefloor
User
Beiträge: 4185
Registriert: Mittwoch 17. Oktober 2007, 21:40
Wohnort: WW
Kontaktdaten:

Hallo,

nee, da ist ein anderer, grundlegender Fehler drin. So wie die letzte Zeile des Traceback aussieht versuchst du, die Daten der CSV-Datei als Dateinamen zu schreiben. Was natürlich nicht geht, weil da Zeilenumbrüche (`\r\n`) drin sind.

Also muss in deinem Progammcode ein Fehler sein.

Gruß, noisefloor
Sirius3
User
Beiträge: 18258
Registriert: Sonntag 21. Oktober 2012, 17:20

Du hast offensichtlich den Inhalt Deines Dataframes als Pfad benutzt. Ohne Code läßt sich dazu nichts sagen, aber wenn ich raten müßte, hast Du nicht nur StringIO sondern auch BytesIO entfernt, was dann eins zu viel war. send_file erwartet als erstes Argument einen Dateinamen oder eben ein file-like Objekt.
Sternenregen
User
Beiträge: 39
Registriert: Mittwoch 13. Januar 2021, 16:17

Das ist der Code.

Code: Alles auswählen

@app.route('/saveResults', methods=['GET', 'POST'])
def save_results():

    dicttest = {"test1": 123, "test2": 123}
    df = pd.DataFrame.from_dict(dicttest, orient='index')
    csvfile = df.to_csv()
    buffered_str = io.StringIO(csvfile)
    bufferd_bytes = io.BytesIO(buffered_str.read().encode("utf-8"))

    return send_file(bufferd_bytes,
              mimetype="text/csv",
              download_name='test.csv',
              as_attachment=True)

Code: Alles auswählen

$(document).ready(function(){
    $('#save-result').click(function(){
        console.log("asdf");
        $.ajax({
            type: 'POST',
            url: '/saveResults',
            success: function(data){
                console.log("successfasdf");
            }
        });
    });
});
Es wird sogar in den Success gesprungen. Aber nichts passiert. Nichts wo gedownloaded wird. Aber ja, ich stimme dir zu. Das ist ein grundlegendes Problem.
Der Mac kriegt es jedenfalls hin.
Sirius3
User
Beiträge: 18258
Registriert: Sonntag 21. Oktober 2012, 17:20

Das ist wieder ein anderer Code, als der, der die Fehlermeldung produziert hat.
Dort stand nämlich

Code: Alles auswählen

return send_file(csv_file,
Korrekt wäre:

Code: Alles auswählen

@app.route('/saveResults', methods=['GET', 'POST'])
def save_results():
    dicttest = {"test1": 123, "test2": 123}
    df = pd.DataFrame.from_dict(dicttest, orient='index')
    buffered_bytes = io.BytesIO()
    df.to_csv(buffered_bytes)
    return send_file(buffered_bytes,
              mimetype="text/csv",
              download_name='test.csv',
              as_attachment=True)
Und was erwartest Du? Natürlich kommt da kein Download. Du machst einen AJAX-Call, dessen Ergebnis von Javascript im Browser verarbeitet wird. Du machst aber mit `data` nichts.
Sternenregen
User
Beiträge: 39
Registriert: Mittwoch 13. Januar 2021, 16:17

Vielen Dank. Darin lag der Denkfehler. Ein dummer Fehler.

Vielen Dank an alle Beteiligten.
Sternenregen
User
Beiträge: 39
Registriert: Mittwoch 13. Januar 2021, 16:17

__blackjack__ hat geschrieben: Sonntag 5. September 2021, 20:48 @Sternenregen: Vielleicht dauert es einfach nur sehr lange und/oder der Speicher reicht nicht aus. Das Vorgehen ist nicht wirklich speicherschonend. Alleine der Code den wir sehen hat das ganze Ergebnis als DataFrame und als Zeichenkette und als Bytes-Objekt gleichzeitig im Speicher. Und einen grossen DataFrame dynamisch aufzubauen ist auch nicht so toll, und wenn da auch alles an Namen gebunden wird, gibt es da das komplette Ergebnis vielleicht *noch mal* komplett im Speicher.

Ich würde mir mal den Speicherverbrauch auf dem Produktivsystem anschauen.
Ok. Was könnte man da dagegen tun?

Jetzt wo es vermeintlich funktioniert, bekomme ich wieder den Rekursionsfehler: Aber jetzt kann ich ihn auch posten:

Code: Alles auswählen

ERROR:app:Exception on /downloadResult [GET]
Traceback (most recent call last):
  File "/usr/local/lib/python3.9/site-packages/flask/app.py", line 2447, in wsgi_app
    response = self.full_dispatch_request()
  File "/usr/local/lib/python3.9/site-packages/flask/app.py", line 1952, in full_dispatch_request
    rv = self.handle_user_exception(e)
  File "/usr/local/lib/python3.9/site-packages/flask/app.py", line 1821, in handle_user_exception
    reraise(exc_type, exc_value, tb)
  File "/usr/local/lib/python3.9/site-packages/flask/_compat.py", line 39, in reraise
    raise value
  File "/usr/local/lib/python3.9/site-packages/flask/app.py", line 1950, in full_dispatch_request
    rv = self.dispatch_request()
  File "/usr/local/lib/python3.9/site-packages/flask/app.py", line 1936, in dispatch_request
    return self.view_functions[rule.endpoint](**req.view_args)
  File "/meineApp/app.py", line 239, in get_result
    buffered_csv = get_result()
  File "/meineApp/app.py", line 239, in get_result
    buffered_csv = get_result()
  File "/meineApp/app.py", line 239, in get_result
    buffered_csv = get_result()
  [Previous line repeated 985 more times]
RecursionError: maximum recursion depth exceeded
Wie könnte man das ganze effizienter gestalten?
Antworten