caching work, some doc content
diff --git a/doc/build/content/caching.txt b/doc/build/content/caching.txt index 495f287..ff532e9 100644 --- a/doc/build/content/caching.txt +++ b/doc/build/content/caching.txt
@@ -1,16 +1,31 @@ ### Caching -Any template or component can be cached using the `cache` argument to the `%page` or `%def` directives: +Any template or component can be cached using the `cache` argument to the `<%page>` or `<%def>` directives: <%page cache="true"/> - template text - + template text + +The above template, after being executed the first time, will store its content within a cache that by default is scoped within memory. Subsequent calls to the template's `render()` method will return content directly from the cache. When the `Template` object itself falls out of scope, its corresponding cache is garbage collected along with the template. + +Caching requires that the `myghtyutils` package be installed on the system. + +The cache flag can also be assigned to any `<%def>`: + <%def name="mycomp" cache="true" cache_timeout="30" cache_type="memory"> other text </%def> +Above, we also illustrate a few more options which are also available on the `<%page>` tag. + Cache arguments: + - cache="false|true" - turn caching on - cache_timeout - number of seconds in which to invalidate the cached data -- cache_type - type of caching. `memory`, `file`, `dbm`, or `memcached`. \ No newline at end of file +- cache_type - type of caching. `memory`, `file`, `dbm`, or `memcached`. +- cache_key - the "key" used to uniquely identify this content in the cache. it defaults to the name of the function (TODO: support for multiple defs with the same name). It is an evaluable tag, so you can put a Python expression to calculate the value of the key on the fly. For example, heres a page that caches any page which inherits from it, based on the filename of the calling template: + + <%page cache="true", cache_key="${self.filename}"/> + + ${next.body()} +
diff --git a/doc/build/content/namespaces.txt b/doc/build/content/namespaces.txt index 66448aa..b12d933 100644 --- a/doc/build/content/namespaces.txt +++ b/doc/build/content/namespaces.txt
@@ -1,7 +1,7 @@ Namespaces ============= -Namespaces are used to organize groups of components into categories, and also to "import" components from other files so that you don't have to type the full filename of the remote component file. +Namespaces are used to organize groups of components into categories, and also to "import" components from other files. If the file `components.html` defines these two components: @@ -21,20 +21,28 @@ Heres comp1: ${comp.comp1()} Heres comp2: ${comp.comp2()} - -The `<%namespace>` tag is more powerful than that. You can also declare `<%defs>` within the namespace: - # define a namespace - <%namespace name="stuff"> - <%def name="comp1"> - comp1 - </%def> - </%namespace> - - # then call it - ${stuff:comp1()} +The `comp` variable above is an instance of `mako.runtime.Namespace`, a **proxy object** which delivers method calls to the underlying template callable using the current context. -Namespaces can also import modules containing regular Python callables. These callables need to take at least one argument, `context`: +`<%namespace>` also provides an `import` attribute which can be used to pull the names into the local namespace, removing the need to call it via the ".". When `import` is used, the `name` attribute is optional. + + <%namespace file="components.html" import="comp1, comp2"/> + + Heres comp1: ${comp1()} + Heres comp2: ${comp2()} + +`import` also supports the "*" operator: + + <%namespace file="components.html" import="*"/> + + Heres comp1: ${comp1()} + Heres comp2: ${comp2()} + +The names imported by the `import` attribute take precedence over any names that exist within the current context. + +### Namespaces from Regular Python Modules + +Namespaces can also import regular Python functions from modules. These callables need to take at least one argument, `context`: A module file `some/module.py` might contain the callable: @@ -48,3 +56,18 @@ ${hw.my_tag()} Note that the `context` argument is not needed in the call; the `namespace` tag creates a locally-scoped callable which takes care of it. + +### Declaring defs in namespaces. + +The `<%namespace>` tag supports the definition of `<%defs>` directly inside the tag. These defs become part of the namespace like any other function, and will override the definitions pulled in from a remote template or module: + + # define a namespace + <%namespace name="stuff"> + <%def name="comp1"> + comp1 + </%def> + </%namespace> + + # then call it + ${stuff:comp1()} +
diff --git a/doc/build/templates/base.html b/doc/build/templates/base.html index a0c4f6e..dc1d084 100644 --- a/doc/build/templates/base.html +++ b/doc/build/templates/base.html
@@ -1,3 +1,4 @@ +<%page cached="True" cache_key="${self.filename}"/> # base.html - common to all documentation pages. intentionally separate # from autohandler, which can be swapped out for a different one <%
diff --git a/lib/mako/codegen.py b/lib/mako/codegen.py index 24d7d1f..71f8152 100644 --- a/lib/mako/codegen.py +++ b/lib/mako/codegen.py
@@ -40,6 +40,7 @@ buffered = eval(node.attributes.get('buffered', 'False')) cached = eval(node.attributes.get('cached', 'False')) defs = None + pagetag = None else: (pagetag, defs) = self.write_toplevel() name = "render" @@ -52,7 +53,7 @@ args = [a for a in ['context'] + args + ['**kwargs']] - self.write_render_callable(name, args, buffered, filtered, cached) + self.write_render_callable(pagetag or node, name, args, buffered, filtered, cached) if defs is not None: for node in defs: @@ -114,7 +115,7 @@ return (pagetag[0], main_identifiers.topleveldefs) - def write_render_callable(self, name, args, buffered, filtered, cached): + def write_render_callable(self, node, name, args, buffered, filtered, cached): """write a top-level render callable. this could be the main render() method or that of a top-level def.""" @@ -136,7 +137,7 @@ self.printer.writeline(None) self.printer.write("\n\n") if cached: - self.write_cache_decorator(name, buffered) + self.write_cache_decorator(node, name, buffered, self.identifiers) def write_module_code(self, module_code): """write module-level template code, i.e. that which is enclosed in <%! %> tags @@ -193,7 +194,7 @@ self.printer.writeline("pass") self.printer.writeline(None) - def write_variable_declares(self, identifiers, toplevel=False): + def write_variable_declares(self, identifiers, toplevel=False, limit=None): """write variable declarations at the top of a function. the variable declarations are in the form of callable definitions for defs and/or @@ -225,6 +226,11 @@ # which cannot be referenced beforehand. to_write = to_write.difference(identifiers.locally_declared) + # if a limiting set was sent, constraint to those items in that list + # (this is used for the caching decorator) + if limit is not None: + to_write = to_write.intersection(limit) + if toplevel and getattr(self.compiler, 'has_ns_imports', False): self.printer.writeline("_import_ns = {}") self.compiler.has_imports = True @@ -288,7 +294,7 @@ self.write_def_finish(node, buffered, filtered, cached) self.printer.writeline(None) if cached: - self.write_cache_decorator(node.name, False) + self.write_cache_decorator(node, node.name, False, identifiers) def write_def_finish(self, node, buffered, filtered, cached): """write the end section of a rendering function, either outermost or inline. @@ -310,19 +316,21 @@ self.printer.writeline("context.write(%s)" % s) self.printer.writeline(None) - def write_cache_decorator(self, name, buffered): + def write_cache_decorator(self, node_or_pagetag, name, buffered, identifiers): """write a post-function decorator to replace a rendering callable with a cached version of itself.""" self.printer.writeline("__%s = %s" % (name, name)) + cachekey = node_or_pagetag.parsed_attributes.get('cache_key', repr(name)) + self.printer.writeline("def %s(context, *args, **kwargs):" % name) + print "LIMIT", node_or_pagetag.undeclared_identifiers() + self.write_variable_declares(identifiers, limit=node_or_pagetag.undeclared_identifiers()) if buffered: self.printer.writelines( - "def %s(context, *args, **kwargs):" % name, - "return _template_cache.get(%s, createfunc=lambda:__%s(context, *args, **kwargs))" % (repr(name), name), + "return _template_cache.get(%s, createfunc=lambda:__%s(context, *args, **kwargs))" % (cachekey, name), None ) else: self.printer.writelines( - "def %s(context, *args, **kwargs):" % name, - "context.write(_template_cache.get(%s, createfunc=lambda:__%s(context, *args, **kwargs)))" % (repr(name), name), + "context.write(_template_cache.get(%s, createfunc=lambda:__%s(context, *args, **kwargs)))" % (cachekey, name), "return ''", None ) @@ -516,7 +524,8 @@ n.accept_visitor(self) def visitIncludeTag(self, node): self.check_declared(node) - + def visitPageTag(self, node): + self.check_declared(node) def visitCallTag(self, node): self.check_declared(node) if node is self.node:
diff --git a/lib/mako/parsetree.py b/lib/mako/parsetree.py index 16cdda0..1db76f3 100644 --- a/lib/mako/parsetree.py +++ b/lib/mako/parsetree.py
@@ -240,7 +240,7 @@ class DefTag(Tag): __keyword__ = 'def' def __init__(self, keyword, attributes, **kwargs): - super(DefTag, self).__init__(keyword, attributes, ('buffered', 'cached'), ('name','filter'), ('name',), **kwargs) + super(DefTag, self).__init__(keyword, attributes, ('buffered', 'cached', 'cache_key', 'cache_timeout', 'cache_type'), ('name','filter'), ('name',), **kwargs) name = attributes['name'] if re.match(r'^[\w_]+$',name): name = name + "()" @@ -273,5 +273,5 @@ class PageTag(Tag): __keyword__ = 'page' def __init__(self, keyword, attributes, **kwargs): - super(PageTag, self).__init__(keyword, attributes, ('cached'), (), (), **kwargs) + super(PageTag, self).__init__(keyword, attributes, ('cached', 'cache_key', 'cache_timeout', 'cache_type'), (), (), **kwargs) \ No newline at end of file