[ANN] pyTenjin 0.8.0 - much faster template engine than Django

2 views
Skip to first unread message

makoto kuwata

unread,
Jun 6, 2009, 12:49:48 PM6/6/09
to kuwata-lab-products
I have released pyTenjin 0.8.0
http://www.kuwata-lab.com/tenjin/

In this release, I included some ideas that Steve suggested.
Thank you very much, Steve.


Changes from 0.7.0
------------------

* !!IMPORTANT!!
HTML helper function 'tagattr()' is renamed to 'tagattrs()'.
(Notice that new 'tagattr()' is added. See below.)

* 'tagattrs()' is changed to add ' ' (space) at the first character.
ex.
(0.7.0) tagattr(klass='error') #=> 'class="error"'
(0.7.1) tagattrs(klass='error') #=> ' class="error"'

* 'tagattrs()' is changed to handle 'checked', 'selected', and
'disabled' attributes.
ex.
>>> from tenjin.helpers.html import *
>>> tagattrs(checked=True, selected='Y', disabled=1)
' checked="checked" selected="selected" disabled="disabled"'
>>> tagattrs(checked=False, selected='', disabled=0)
''


Bugfix
------

* !!IMPORTANT!!
Template caching is changed to keep template file's timestamp
instead of create time of cached object. See
http://groups.google.com/group/kuwata-lab-products/browse_thread/thread/a0d447c282fb383d#msg_de39557405c9b656
for details. (Thanks Steve)


Enhancements
------------

* Add new HTML helper function 'tagattr()'.
(Notice that 'tagattr()' in 0.7.0 is renamed into 'tagattrs()'.)
ex.
>>> from tenjin.helpers.html import *
>>> tagattr('size', 20)
' size="20"'
>>> tagattr('size', 0)
''
>>> tagattr('size', 20, 'large')
' size="large"'
>>> size = 20 # you can use tagattrs() instead of tagattr
()
>>> tagattrs(size=(size and 'large'))
' size="large"'

* Add new HTML helper function 'new_cycle()'.
ex.
>>> from tenjin.helpers.html import *
>>> cycle = new_cycle('odd, 'even')
>>> cycle()
'odd'
>>> cycle()
'even'
>>> cycle()
'odd'
>>> cycle()
'even'

* (experimental) Template converter is changed to add dummy if-
statement
when first Python statement is indented. (Thanks Steve)
ex.
$ cat ex.pyhtml
<html>
<body>
<ul>
<?py for item in items: ?>
<li>${item}</li>
<?py #end ?>
</ul>
</body>
</html>
$ pytenjin -sb ex.pyhtml
_buf.extend(('''<html>
<body>
<ul>\n''', ));
if True: ## dummy
for item in items:
_buf.extend((''' <li>''', escape(to_str(item)),
'''</li>\n''', ));
#end
_buf.extend((''' </ul>
</body>
</html>\n''', ));

* Update User's Guide and FAQ.


Have fun!

--
regards,
makoto kuwata

Steve

unread,
Jun 6, 2009, 2:10:58 PM6/6/09
to kuwata-lab-products
Hi Makoto,

Thank you very much for the new release. I would like to suggest one
small cleanup and one small refinement. The small cleanup is that you
no longer use the time import so I remove it to reduce startup
latency. The small refinement is that I would like to suggest using
lazy imports also to reduce startup latency.

Below please find a diff against pyTenjin 0.7.0 illustrating my
suggestion. I'm sorry it is not against 0.8.0 but I still need to
merge in your latest changes.

Thanks!
--Steve


diff -r 8e7d805dae62 GAE/tenjin.py
--- a/GAE/tenjin.py Sat Jun 06 09:35:49 2009 -0700
+++ b/GAE/tenjin.py Sat Jun 06 11:03:17 2009 -0700
@@ -34,7 +34,15 @@
__all__ = ['Template', 'Engine', 'helpers', 'html', ]


-import re, sys, os, time, marshal
+import re, sys, os
+
+# Lazy import
+random = None
+marshal = None
+pickle = None
+memcache = None
+unquote = None
+
python3 = sys.version_info[0] == 3
python2 = sys.version_info[0] == 2

@@ -46,9 +54,11 @@
##

def _write_binary_file(filename, content):
+ global random
f = None
try:
- import random
+ if random is None:
+ import random
tmpfile = filename + str(random.random())[1:]
f = open(tmpfile, 'wb')
f.write(content)
@@ -225,9 +235,11 @@

def _decode_params(s):
"""decode <`#...#`> and <`$...$`> into #{...} and ${...}"""
- import urllib
- if python2: from urllib import unquote
- elif python3: from urllib.parse import unquote
+ global unquote
+ if unquote is None:
+ import urllib
+ if python2: from urllib import unquote
+ elif python3: from urllib.parse import unquote
dct = { 'lt':'<', 'gt':'>', 'amp':'&', 'quot':'"',
'#039':"'", }
def unescape(s):
#return s.replace('&lt;', '<').replace('&gt;',
'>').replace('&quot;', '"').replace('&#039;', "'").replace('&amp;',
'&')
@@ -845,6 +857,11 @@


class MarshalCacheStorage(FileCacheStorage):
+ def __init__(self, postfix='.cache'):
+ global marshal
+ if marshal is None:
+ import marshal
+ super(MarshalCacheStorage, self).__init__(postfix)

def _load(self, fullpath):
cachepath = self._cachename(fullpath)
@@ -860,10 +877,14 @@


class PickleCacheStorage(FileCacheStorage):
+ def __init__(self, postfix='.cache'):
+ global pickle
+ if pickle is None:
+ try: import cPickle as pickle
+ except: import pickle
+ super(PickleCacheStorage, self).__init__(postfix)

def _load(self, fullpath):
- try: import cPickle as pickle
- except: import pickle
cachepath = self._cachename(fullpath)
if not os.path.isfile(cachepath): return None
if logger: logger.info("[tenjin.PickleCacheStorage] load
cache (file=%s)" % repr(cachepath))
@@ -871,8 +892,6 @@
return pickle.loads(dump)

def _store(self, fullpath, dict):
- try: import cPickle as pickle
- except: import pickle
if 'bytecode' in dict: dict.pop('bytecode')
cachepath = self._cachename(fullpath)
if logger: logger.info("[tenjin.PickleCacheStorage] store
cache (file=%s)" % repr(cachepath))
@@ -937,16 +956,17 @@
def __init__(self, lifetime=None, postfix='.cache'):
CacheStorage.__init__(self, postfix)
if lifetime is not None: self.lifetime = lifetime
+ global memcache
+ if memcache is None:
+ from google.appengine.api import memcache

def _load(self, fullpath):
- from google.appengine.api import memcache
key = self._cachename(fullpath)
if logger: logger.info("[tenjin.GaeMemcacheCacheStorage] load
cache (key=%s)" % repr(key))
return memcache.get(key)

def _store(self, fullpath, dict):
if 'bytecode' in dict: dict.pop('bytecode')
- from google.appengine.api import memcache
key = self._cachename(fullpath)
if logger: logger.info("[tenjin.GaeMemcacheCacheStorage]
store cache (key=%s)" % repr(key))
ret = memcache.set(key, dict, self.lifetime)
@@ -954,7 +974,6 @@
if logger: logger.info("[tenjin.GaeMemcacheCacheStorage:
failed to store cache (key=%s)" % repr(key))

def _delete(self, fullpath):
- from google.appengine.api import memcache
memcache.delete(self._cachename(fullpath))


@@ -1092,19 +1111,19 @@
assert filename and fullpath
cache = self.cache
template = cache and cache.get(fullpath, self.templateclass)
or None
+ mtime = os.path.getmtime(filename)
if template:
assert template.timestamp is not None
- if template.timestamp < os.path.getmtime(filename):
+ if template.timestamp != mtime:
#if cache: cache.delete(path)
template = None
if logger: logger.info("[tenjin.Engine] cache is old
(filename=%s, template=%s)" % (repr(filename), repr(template)))
if not template:
- curr_time = time.time()
if self.preprocess: ## required for preprocess
if _context is None: _context = {}
if _globals is None: _globals = sys._getframe
(1).f_globals
template = self._create_template(filename, _context,
_globals)
- template.timestamp = curr_time
+ template.timestamp = mtime
if cache:
if not template.bytecode: template.compile()
cache.set(fullpath, template)

makoto kuwata

unread,
Jun 15, 2009, 10:53:07 AM6/15/09
to kuwata-lab-products
Thank you, Steve.
I included your patch.
Please wait next release.

--
regards,
makoto kuwata
Reply all
Reply to author
Forward
0 new messages