Bottle: HTML <img src...> aus String

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
Antworten
Benutzeravatar
noisefloor
User
Beiträge: 4209
Registriert: Mittwoch 17. Oktober 2007, 21:40
Wohnort: WW
Kontaktdaten:

Hallo,

ich generiere mit ReportLab einen Graphen und würde diesen gerne (mittels Bottle) auf einer Webseite ausgeben. Den Graphen möchte ich aber nicht auf der HD speichern, sondern in einen String bzw. cStringIO-Objekt schreiben. Das Problem ist, dass HTML `<img src="...">` eine Datei erwartet...

Der Code, der nicht funktioniert, sieht so aus:

Code: Alles auswählen

#!/usr/bin/env python
# -*- coding: utf-8 -*

import bottle
import cStringIO
from PIL import Image as PILI
from reportlab.graphics.shapes import *
from reportlab.graphics import renderPM
from reportlab.graphics.charts.piecharts import Pie

@bottle.route('/test')
def test():
    graph = build_graph()
    return bottle.template('html_test.tpl',graph = graph)
    
def build_graph():
    buf = cStringIO.StringIO()
    d = Drawing(200,100)
    pc = Pie()
    pc.x = 65
    pc.y = 15
    pc.width = 70
    pc.height = 70
    pc.data = [10,20,30,40]
    pc.labels = ['a','b','c','d']
    d.add(pc)
    renderPM.drawToFile(d,buf,'PNG')
    buf.reset()
    im = PILI.open(buf)
    im.load()
    return im
    
bottle.debug(True)
bottle.run(reloader=True)
und das Template:

Code: Alles auswählen

<h1>Graph Test</h1>
<p>Text Text Text 1</p>
<img src="{{graph}}" alt="Ein Graph">
<p>Text Text Text 2</p>
Ich bin mir noch nicht mal sicher, ob und wie man dafür PIL braucht... Der oben gezeigte Code war der letzte Versuch...

Gruß, noisefloor
Zuletzt geändert von noisefloor am Montag 11. Oktober 2010, 19:56, insgesamt 1-mal geändert.
Benutzeravatar
snafu
User
Beiträge: 6881
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Trage in das HTML doch einfach einen Pseudo-Dateipfad ein. Diesen kannst du dann mit Bottle als Route abfangen und dort die Datei mittels `static_file()` (oder so ähnlich) zurückgeben. Man müsste nur wissen, ob die Funktion auch mit dateiartigen Objekten umgehen kann oder wirklich eine echte Datei erwartet. Alternativ könnte man natürlich noch kurzzeitig eine temporäre Datei anlegen und diese dann zurückgeben. Das ist aber sicher nicht so schön.

Übrigens noch eine andere Sache am Rande, da ich bei dem Thema daran gedacht habe, das früher oder später Caching relevant werden könnte: Wie ich heute entdeckt habe, wird das Entwicklung befindliche Python 3.2 einen Dekorator haben, der Funktionsrückgabewerte cachen kann: @functools.lru_cache. Das könnte bestimmt gerade für Anwendungen wie Bottle oder Flask interessant werden.
Zuletzt geändert von snafu am Samstag 9. Oktober 2010, 20:58, insgesamt 1-mal geändert.
Benutzeravatar
noisefloor
User
Beiträge: 4209
Registriert: Mittwoch 17. Oktober 2007, 21:40
Wohnort: WW
Kontaktdaten:

Hallo,

in der "echten" Version kann eine HTML-Seite so bis zu 10 Grafiken / Graphen enthalten und es gibt ~10 Seiten. Von daher wäre der Weg via eigene Route etwas mühselig.

Gruß, noisefloor
Benutzeravatar
Hyperion
Moderator
Beiträge: 7478
Registriert: Freitag 4. August 2006, 14:56
Wohnort: Hamburg
Kontaktdaten:

noisefloor hat geschrieben:Hallo,

in der "echten" Version kann eine HTML-Seite so bis zu 10 Grafiken / Graphen enthalten und es gibt ~10 Seiten. Von daher wäre der Weg via eigene Route etwas mühselig.

Gruß, noisefloor
Wieso das? Es reicht doch eine Route, in der Du einen Schlüssel übergibst, anhand dessen Du dann den Graphen ausliefern kannst.
encoding_kapiert = all(verstehen(lesen(info)) for info in (Leonidas Folien, Blog, Folien & Text inkl. Python3, utf-8 everywhere))
assert encoding_kapiert
Benutzeravatar
snafu
User
Beiträge: 6881
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Du sollst ja nicht unbedingt für jede Route händisch eine eigene Funktion anlegen. Auch Bilddateien können zusätzlich Argumente für einen Query-String haben. Dort würden dann meinetwegen die Koordinaten drin sein oder was auch immer du genau vor hast. Siehe dazu das Bottle-Tutorial im Abschnitt Accessing Request Data entsprechend der Unterpunkt "Query Strings".

Übrigens finde ich diesen Weg schon relativ elegant und es ist auch der einzig sinnvolle - wenn nicht gar: mögliche - Weg, der mir dafür einfällt.
Benutzeravatar
snafu
User
Beiträge: 6881
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Gerade gesehen, dass `static_file()` wohl zwingend einen Dateipfad erwartet und in der API für die neue Version von Bottle seh ich's igendwie gar nicht mehr. Vermutlich ist es dann am besten, wenn die Funktion einfach den Inhalt von `StringIO()` zurückgibt. Eventuell musst du da noch etwas tricksen, damit die Browser es korrekt als Grafik interpretieren. Aber vielleicht wissen auch Defnull oder jemand anders eine gute Lösung dafür.
Benutzeravatar
noisefloor
User
Beiträge: 4209
Registriert: Mittwoch 17. Oktober 2007, 21:40
Wohnort: WW
Kontaktdaten:

Hallo,

ok... werde mal beide Varianten probieren (also Route und Temporäre Datei).

Werde es wahrscheinlich erstmal mit cStringIO probieren und den Content händisch als 'PNG' deklarieren. Das sollte funktionieren, zumindest funktioniert das Äquivalent mit PDFs.

Dann noch eine Frage zu temporären Dateien: Wenn ich diesen Weg wähle -> wann werden temporäre Dateien (unter Linux) automatisch gelöscht? Nach einer Zeit X oder nur beim Reboot?

Gruß, noisefloor
BlackJack

@noisefloor: Temporäre Dateien werden gar nicht automatisch gelöscht.
Benutzeravatar
noisefloor
User
Beiträge: 4209
Registriert: Mittwoch 17. Oktober 2007, 21:40
Wohnort: WW
Kontaktdaten:

Hallo,

also:

Code: Alles auswählen

#!/usr/bin/env python
# -*- coding: utf-8 -*

import bottle
import cStringIO
from reportlab.graphics.shapes import *
from reportlab.graphics import renderPDF, renderPM
from reportlab.graphics.charts.piecharts import Pie

@bottle.route('/test')
def test():
    return bottle.template('html_test.tpl')

@bottle.route('/graph')   
def build_graph():
    buf = cStringIO.StringIO()
    d = Drawing(200,100)
    pc = Pie()
    pc.x = 65
    pc.y = 15
    pc.width = 70
    pc.height = 70
    pc.data = [10,20,30,40]
    pc.labels = ['a','b','c','d']
    d.add(pc)
    renderPM.drawToFile(d,buf,'PNG')
    buf.reset()
    bottle.response.headers['Content-Type'] = 'image/png'
    return buf.read()
        
bottle.debug(True)
bottle.run(reloader=True)
liefert auf der Route '/graph' brav die Grafik zurück, aber bei

Code: Alles auswählen

<h1>Graph Test</h1>
<p>Text Text Text 1</p>
<img scr="http://localhost:8080/graph" alt="Ein Diagramm">
<p>Text Text Text 2</p>
passiert nix bzw. es wird das 'alt'-Attribut ausgegeben.

Wie snafu richtig sagt verlangt 'bottle.static_file()' eine reale Datei.

Bei Verwendung von temporären Dateien hätte ich halt das Problem, das mein /temp Verzeichnis wächst, weil die temporären Dateien ja nicht löschen kann, bevor Sie mit dem Template returniert werden...

Gruß, noisefloor
Darii
User
Beiträge: 1177
Registriert: Donnerstag 29. November 2007, 17:02

noisefloor hat geschrieben:Das Problem ist, dass HTML `<img src="...">` eine Datei erwartet...
Nein, nicht zwingend. Fast alle Browser (selbst IE) beherrschen inzwischen das data-URL-Schema. Du kannst das Bild also einfach base64-kodiert in die Datei schreiben.
Benutzeravatar
noisefloor
User
Beiträge: 4209
Registriert: Mittwoch 17. Oktober 2007, 21:40
Wohnort: WW
Kontaktdaten:

Hallo,

@Darii: ah, interessant. Probiere ich heute abend mal aus.

Gruß, noisefloor
Benutzeravatar
snafu
User
Beiträge: 6881
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Code: Alles auswählen

from base64 import b64encode
from mimetypes import guess_type

def get_data_uri(fileobj, mime_type=None):
    if not mime_type and hasattr(fileobj, 'name'):
        mime_type = guess_type(fileobj.name)[0]
    if not mime_type:
        # should of course use a special exception type
        raise Exception, 'Could not guess missing MIME-type'
    data = b64encode(fileobj.read())
    return 'data:{0};base64,{1}'.format(mime_type, data)
Benutzeravatar
noisefloor
User
Beiträge: 4209
Registriert: Mittwoch 17. Oktober 2007, 21:40
Wohnort: WW
Kontaktdaten:

Hallo,

grundsätzlich sollte folgender Code funktionieren:

Code: Alles auswählen

#!/usr/bin/env python
# -*- coding: utf-8 -*

import bottle
import cStringIO
from mimetypes import guess_type
from base64 import b64encode
from reportlab.graphics.shapes import *
from reportlab.graphics import renderPDF, renderPM
from reportlab.graphics.charts.piecharts import Pie

@bottle.route('/test')
def test():
    graph = build_graph()
    return bottle.template('html_test.tpl',graph=graph)

def build_graph():
    buf = cStringIO.StringIO()
    d = Drawing(200,100)
    pc = Pie()
    pc.x = 65
    pc.y = 15
    pc.width = 70
    pc.height = 70
    pc.data = [10,20,30,40]
    pc.labels = ['a','b','c','d']
    d.add(pc)
    renderPM.drawToFile(d,buf,'PNG')
    buf.reset()
    graph_uri = get_data_uri(buf,mime_type='png')
    return graph_uri
    
def get_data_uri(fileobj, mime_type=None):
    if not mime_type and hasattr(fileobj, 'name'):
        mime_type = guess_type(fileobj.name)[0]
    if not mime_type:
        # should of course use a special exception type
        raise Exception, 'Could not guess missing MIME-type'
    data = b64encode(fileobj.read())
    return 'data:image/{0};base64,{1}'.format(mime_type, data)

       
bottle.debug(True)
bottle.run(reloader=True)

Code: Alles auswählen

<h1>Graph Test</h1>
<p>Text Text Text 1</p>
<img scr="{{!graph}}" alt="Ein Diagramm" />
<p>Text Text Text 2</p>
ABER: Bottle funkt irgendwie dazwischen... Beim `{{!graph}}` zerlegt Bottle den einzeiligen String scheinbar in mehrere Zeilen, wodurch der Browser die URI nicht mehr korrekt interpretiert. Fügt man das Ergebnis von `'data:image/{0};base64,{1}'.format(mime_type, data)` händisch als HTML-Code ein, zeigt der Browser das Diagramm an.

Ergo: Ein Bottle-Problem ?!

BTW: kann ein Moderator den Thread mal ins Webframework-Forum schieben? Danke. :-)

Gruß, noisefloor
Antworten