Cheetah hat eine Möglichkeit eingebaut, Werte, die die Vorlage schon einmal errechnet oder von einer Python-Funktion erhalten hat, im Speicher zu halten. Bei einem nochmaligen Aufruf der Seite müssen diese Berechnungen nicht noch einmal ausgeführt werden. Im oben gezeigten Beispiel wird die Vorlage jedes mal neu aus der TMPL-Datei erstellt und alle Funktionen werden neu ausgeführt. Damit wird jedes Caching von Cheetah schon im Ansatz abgewürgt. Um das zu verhindern, muss das geladene Template in einer Liste oder in einem Dictionary im Speicher gehalten werden. Die Schwierigkeit dabei ist allerdings, die Anzahl der zwischengespeicherten Vorlagen einzuschränken und auch nach einer gewissen Zeit automatisch wieder aus dem Container zu löschen.
Da auf diese Liste von mehreren Threads aus zugegriffen werden kann, muss der Zugriff auf diesen Container threadsicher sein. Das ist nicht mit wenigen Zeilen Code zu machen. Deshalb erscheint das nächste Beispiel ein wenig groß. Dafür hat es aber die oben schon erwähnten Vorteile. Es sind zwar mehr als 200 Codezeilen, aber ich möchte dir das Programm nicht vorenthalten, da es die Arbeit mit CherryPy und Cheetah erheblich erleichtern kann.
#!/usr/bin/env python
# -*- coding: iso-8859-15 -*-
# cptest.py
import os
import cherrypy
from Cheetah.Template import Template
import time
import threading
APPDIR = os.path.dirname(os.path.abspath(__file__))
INI_FILENAME = os.path.join(APPDIR, "cptest.ini")
class CheetahTemplateContainer(dict):
def __init__(
self,
max_items_in_container = 1000,
max_minutes_in_container = 120,
vacuum_interval_minutes = 2.9,
max_lock_tries = 300
):
"""
TemplateContainer initialisieren und Einstellungen übernehmen
:param max_items_in_container: Gibt an, wie viele Vorlagen maximal im
Container zwischengespeichert bleiben. Ist diese Höchstgrenze
überschritten, dann werden die ältesten Vorlagen aus dem Container
entfernt
:param max_minutes_in_container: Gibt an, wie lange eine Vorlage maximal
im Container bleiben darf. Ist diese Zeit überschritten, dann wird
die Vorlage aus dem Container entfernt.
:param vacuum_interval_minutes: Gibt an, in welchem Intervall die
Vacuum-Funktion aufgerufen werden soll. Diese Vacuum-Funktion ist
dafür zuständig, die Vorlagen wieder aus dem Container zu entfernen.
:param max_lock_tries: Beim Anfordern einer Vorlage, wird versucht einen
Lock zu acquirieren, damit sich Threads nicht in die Quere kommen.
Damit es nicht zu einem kompletten Blockieren der Auslieferung kommen
kann, wird beim Acquirieren des Locks nicht gewartet, bis der Lock
akzeptiert wurde. Es wird <max_lock_tries> mal versucht, einen Lock
zu bekommen. Zwischen den Versuchen wird jeweils 0.1 Sekunde gewartet.
Konnte nach <max_lock_tries> Versuchen kein Lock acquiriert werden,
dann wird cherrypy.TimeoutError() ausgelöst. Standard: 300 = ca. 30 sec.
"""
dict.__init__(self)
self.max_items_in_container = max_items_in_container
self.max_minutes_in_container = max_minutes_in_container
self.vacuum_interval_minutes = vacuum_interval_minutes
self.max_lock_tries = max_lock_tries
self._filenames_list = [] # Speichert die Ladereihenfolge
self._lock = threading.Lock()
#Vacuum-Timer starten
self._last_vacuum_time = time.mktime(time.gmtime())
self._vacuum_timer = threading.Timer(5, self._autovacuum)
self._vacuum_timer.start()
def _autovacuum(self):
"""
Löscht Vorlagen aus den Listen und aus dem Dictionary falls diese
älter als die angegebene Zeitspanne MAX_MINUTES_IN_CONTAINER ist.
Es werden auch die ältesten Vorlagen aus den Listen und aus dem
Dictionary gelöscht falls die Anzahl MAX_ITEMS_IN_CONTAINER
überschritten wurde.
Achtung!
Diese Funktion wird alle 5 sec. ausgeführt. Wenn die eingestellte Zeit
für das Vacuum aber noch nicht gekommen ist, dann wird die Ausführung
abgebrochen.
"""
try:
# Prüfen ob die Zeit für das Vacuum gekommen ist
if (
self._last_vacuum_time + (self.vacuum_interval_minutes * 60.0) >
time.mktime(time.gmtime())
):
return
# Wenn die Engine nicht mehr läuft, dann Vacuum stoppen
if not cherrypy.engine.state:
return
# Ab jetzt wird mit einem Lock gesperrt
self._lock.acquire(True)
try:
cherrypy.log.error("Autovacuum BEGIN")
# Wenn MAX_ITEMS_IN_CONTAINER überschritten wurde, dann werden
# die ältesten Vorlagen aus dem Dict und aus der Liste gelöscht.
files_count = len(self._filenames_list)
if files_count > self.max_items_in_container:
for i in range(files_count - self.max_items_in_container):
filename = self._filenames_list.pop()
dict.__delitem__(self, filename)
cherrypy.log.error("Autovacuum released the file: '%s'" % repr(filename))
# Wenn MAX_MINUTES_IN_CONTAINER überschritten wurde, dann wird die
# Vorlage aus dem Dict und aus der Liste gelöscht.
now = time.mktime(time.gmtime())
for filename in self.keys():
template = dict.__getitem__(self, filename)
if (template._loadtime + (self.max_minutes_in_container * 60.0)) < now:
try:
self._filenames_list.remove(filename)
except ValueError:
pass
dict.__delitem__(self, filename)
cherrypy.log.error("Autovacuum released the file: '%s'" % repr(filename))
cherrypy.log.error("Autovacuum END")
finally:
self._lock.release()
self._last_vacuum_time = time.mktime(time.gmtime())
# Lock aufgehoben und Vacuumzeit aktualisiert
finally:
if cherrypy.engine.state:
self._vacuum_timer = threading.Timer(5, self._autovacuum)
self._vacuum_timer.start()
else:
self._vacuum_timer.cancel()
cherrypy.log.error("Autovacuum stopped")
def load_templatefile(self, filename):
"""
Vorlage laden und mit den Attributen _mtime und _ctime versehen.
Die Vorlage wird dabei in die Dateinamenliste und in das Dict eingetragen.
:return: Gibt die geladene Vorlagegeninstanz zurück
"""
template = Template(file = filename)
mtime = os.path.getmtime(filename)
# Interne MTime und CTime
template._mtime = mtime
template._loadtime = time.mktime(time.gmtime())
# MTime-String und CTime-String für die Verwendung im HTML-Kopf
template.mtime = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(mtime))
template.ctime = time.strftime(
"%Y-%m-%d %H:%M:%S", time.localtime(os.path.getctime(filename))
)
self._filenames_list.append(filename)
dict.__setitem__(self, filename, template)
return template
def __getitem__(self, filename):
"""
Wird aufgerufen, wenn ein Template aus dem Dict abgerufen wird.
:param filename: Vollständiger Pfad zur Vorlagendatei
"""
# Versucht einen nicht blockierenden Lock zu bekommen
for i in xrange(self.max_lock_tries):
if self._lock.acquire(False):
break
time.sleep(0.1)
else:
raise cherrypy.TimeoutError()
try:
if filename in self:
template = dict.__getitem__(self, filename)
# Prüfen ob sich das Template im Dateisystem geändert hat
if template._mtime == os.path.getmtime(filename):
return template
else:
# Alten Dateinamen aus Liste entfernen, neu laden und
# zurück geben
try:
self._filenames_list.remove(filename)
except ValueError:
pass
return self.load_templatefile(filename)
else:
# Template laden, in dict eintragen und zurück geben
return self.load_templatefile(filename)
finally:
self._lock.release()
def __setitem__(self, *args, **kwargs):
"""
Das direkte Zuweisen von Einträgen ist nicht erlaubt
"""
raise RuntimeError("Direct asigning not allowed!")
class Root(object):
def __init__(self, max_lock_tries = 300):
"""
Root initialisieren
:param max_lock_tries: Vorlagen, die in der "default"-Methode automatisch
abgearbeitet werden, sollen nicht gleichzeitig von mehreren Threads
abgearbeitet werden. Um die Threads die Vorlagen hintereinander
abarbeiten zu lassen, wird ein Lock acquiriert.
Damit es nicht zu einem kompletten Blockieren der Auslieferung kommen
kann, wird beim Acquirieren des Locks nicht gewartet, bis der Lock
akzeptiert wurde. Es wird <max_lock_tries> mal versucht, einen Lock
zu bekommen. Zwischen den Versuchen wird jeweils 0.1 Sekunde gewartet.
Konnte nach <max_lock_tries> Versuchen kein Lock acquiriert werden,
dann wird cherrypy.TimeoutError() ausgelöst. Standard: 300 = ca. 30 sec.
"""
# In diesem Container werden alle Cheetah-Vorlagen verwaltet, die
# automatisch geladen wurden.
self.templatecontainer = CheetahTemplateContainer()
# Vorlagen sollten *hintereinander* befüllt werden. Das wird mit dieser
# Sperre realisiert.
self.max_lock_tries = max_lock_tries
self._template_lock = threading.Lock()
def default(self, *args, **kwargs):
"""
Standard-Handler für Objekte, die keiner anderen Handler-Funktion
zugewiesen werden konnten.
TMPL-Dateien werden hier auch ohne zugewiesener Funktion abgearbeitet
und ausgeliefert.
"""
filename = os.path.join(APPDIR, *args)
if os.path.isdir(filename):
# Handelt es sich um die index.tmpl?
filename = os.path.join(filename, "index.tmpl")
if os.path.isfile(filename):
# Fehlenden Slash anhängen
request = cherrypy.request
path_info = request.path_info
if not path_info.endswith('/'):
new_url = cherrypy.url(path_info + '/', request.query_string)
raise cherrypy.HTTPRedirect(new_url)
else:
raise cherrypy.NotFound()
else:
# Benannte Vorlage
if filename.lower().endswith(".tmpl"):
if not os.path.isfile(filename):
raise cherrypy.NotFound()
else:
raise cherrypy.NotFound()
# Versucht einen nicht blockierenden Lock zu bekommen
for i in xrange(self.max_lock_tries):
if self._template_lock.acquire(False):
break
time.sleep(0.1)
else:
raise cherrypy.TimeoutError()
try:
# Vorlage holen
template = self.templatecontainer[filename]
# Request und Response zur Vorlage hinzu fügen
template.request = cherrypy.request
template.response = cherrypy.response
# Vorlage rendern und zurück geben
return str(template)
finally:
# Sperre aufheben
self._template_lock.release()
default.exposed = True
def main():
cherrypy.quickstart(Root(), config = INI_FILENAME)
if __name__ == "__main__":
main()
Der Code ist zwar gut kommentiert, aber er ist nicht einfach zu verstehen. Die Klasse "CheetahTemplateContainer" stellt ein verändertes Dictionary dar. In diesem Dictionary werden die Vorlagen verwaltet. Sie werden darin geladen und mit dem Ladezeitpunkt gekennzeichnet. Das ist alles notwendig, damit die Funktion "_autovacuum" alle paar Minuten durch die geladenen Vorlagen fegen kann um die ältesten Vorlagen wieder aus dem Container raus zu schmeißen.
Näher möchte ich nicht auf das Beispiel eingehen. Es ist Threading im Spiel und ist für Anfänger evt. nicht so leicht zu verstehen. Aber keine Sorge, das kommt mit der Zeit. ;-)
Ich programmiere Progressive Web Applications, Mobile Apps, Desktop-Programme und noch vieles mehr. Falls es dich interessiert, findest du mehr Informationen darüber auf meiner Business-Website.