Hello, Russell Keith-Magee
Thanks for your advices.
*1. Detailed Timeline :*
- Designing concept : this step requires fully design writing for
achieving BACKWARD compatibility for all tags, and filters. Providing
backward-compatibility is a simple task. I've described it earlier by using
FilterExpression class.
- Coding "template compilation" subsystem :
- 4 days term - django.template.loader, django.template.loaders.* -
adding support for importing already compiled templates.
- 1 week term - implementing 'compile' method to
django.template.Template class, in this method will be processed
calling of
child nodelist compiling and writing to compiled template file.
- 2 weeks term - implementing 'compile' method to django.templates
- 1 weeks term - implementing 'compile' method to
django.template.loader_tags Node-derived classes.
- 3 days term - implementing support in
django.template.FilterExpression and django.template.Variable.In 5
weeks term will be produced such possibilities :
1. compiling, writing, importing compiled tempaltes files
2. support for compilation of default filters from
django.template.defaultfilters, and default tags from
django.template.defaulttags : , Node, Node-derived classes : TextNode,
VariableNode, BlockNode, ExtendsNode, ConstantIncludeNode,
IncludeNode,
AutoEscapeNode, CommentNode, CsrfTokenNode, CycleNode, FilterNode,
FirstOfNode, ForNode, IfChangedNode, IfEqualNode,
RegroupNode, SsiNode,
LoadNode, NowNode, SpacelessNode, URLNode, WidthRatioNode, WithNode.
- 5 days term - implementing 'compile' method for
django.template.defaulttags.IfNode and implementing compilation
support for
django.template.smartif.
- Writing unit-tests :
- 1 week term - writing unit-tests for compilation process for
django.template.defaultfilters.
- 2 week term - writing unit-tests for django.template.defaulttags.
- 4 days term - writing unit-tests for django.template.loader_tags.
- 1 week term - writing unit-tests for compilation process for
django.template.Template, django.template.loader,
django.template.loaders.*.
Testing compiled tempates writing,import'ing and raw template compiling.
- 1-2 weeks term - resolving all errors founded by using unit-tests.
By this step I will produce fully tested template compilation code.
- 3-5 weeks term - optimization of written code
*2. Armin Ronacher has good template engine that supports template
compilation, and other features. I can not argue against his template engine
solution. But, society should be pointed on :*
- The default syntax of Jinja2 matches Django syntax in many ways.
However this similarity doesn’t mean that you can use a Django template
unmodified in Jinja2. For example filter arguments use a function call
syntax rather than a colon to separate filter name and arguments.
Additionally the extension interface in Jinja is fundamentally different
from the Django one which means that your custom tags won’t work any
longer.
- Armin Ronacher wrote about backward-compatibilty: > Are you planning on
keeping the API for registering template tags > compatible?
(django.template.Library.register.tag) It will be supported because
otherwise upgrading won't be possible, but how well this works in detail
would have to be investigated and what a cleaner implementation looks like.
Jinja2 has an expression parser and encourages people to create a AST that
does what they want in extensions. However in Jinja2 I never encouraged
people to write custom tags so the API is very limited there. ---- As for
me, he haven't made decision how it would be looked like in Django. I have
already proposed solution of template subsystem's improvement.
- My solution will come with django 'out of the box' against Armin
Ronacher solution.
On Fri, Apr 8, 2011 at 4:23 AM, Russell Keith-Magee <[email protected]
> wrote:
> On Fri, Apr 8, 2011 at 9:15 AM, Andrey Zubko <[email protected]> wrote:
> > Hello,
> > I'm Andrey Zubko, student faculty of Computer Science in Zaporozhye
> > State Engineering Academy. I'm senior chief developer of a local
> > Internet Service Provider "Telza" which provides Internet and
> > telephony services. My responsibilities includes enhancing, improving
> > existent Billing system that is written by myself.
> >
> > In GSOC program I want to improve template subsystem by integrating
> > template compilation into python bytecode.
> >
> > Goals and challenges
> > Integrating of template compilation has 2 goals. The first goal and
> > the chief goal is to provide backward-compatibility to templates,
> > custom tags, custom filters that are already written. The second
> > goal is to minimize modifications of Django sources to provide more
> > stability and faster integrating in trunk.
> > Implementation
> > Support of template compilation can be achieved by adding method
> > 'compile' to every Node-derived class, and some other classes,
> > functions, such as django.template.Variable,
> > django.template.FilterExpression, django.template.NodeList, and others
> > described further.
> >
> > 1. Node and Node-derived classes modifications
> > Class Node and derived from it classes should have optional method –
> > compile, it returns list of python bytecode commands for write in
> > compiled template. It receives parameter parent_name that will
> > contains name of uppermost tag/block. Parent_name is a name of
> > uppermost tag/block that identifies entry in hash blocks_output that
> > is used for caching all outputs. Only one case can provide parent_name
> > to be overridden – meet of the '{% block %}' tag in a parent tag, this
> > tag should override parent_name to its unique name. Approach for
> > caching all output is used for providing template inheritance ability.
> > Use of template Inheritance provides following cases :
> > child template has one sequence of blocks, but parent template has
> > another sequence of blocks
> > child template has blocks that haven't specified in parent's template
> >
> > For solving this cases I provided list blocks_sequence_output that
> > keeps sequence of blocks outputting. Implementation of 'compile'
> > method in Node class :
> > def compile(self,parent_name):
> > self.generate_name(parent_name)
> > compiled_code = ["t = Template('%s')" % self.template,
> > "blocks_output['%s'] = blocks_output['%s'] +
> > t._render(context)" % (self.parent_name,self.parent_name)]
> > return compiled_code
> >
> > As you saw, in purposes of providing backward-compatibility all Node-
> > derived classes, that haven't implemented 'compile' method, will use
> > current rendering approach.
> > Node-derived classes with implemented 'compile' method will be looked
> > like :
> > class TextNode(Node):
> > def compile(self,parent_name):
> > return ["blocks_output['%s'] = blocks_output['%s'] + \"\"\"%s
> > \"\"\"" % (self.parent_name,
> >
> > self.parent_name,
> >
> > self.s)]
> >
> > As shown before 'compile' method is not so difficult to implement.
> > To recursively compilation of Node-derived classes is used method
> > 'compile' in the class NodeList :
> > class NodeList(list):
> > def generate_name(self,node):
> > return id(node)
> >
> > def compile(self, parent_name=None):
> > bits = []
> > for node in self:
> > if isinstance(node, Node):
> > if parent_name is None or isinstance(node,
> > BlockNode) :
> > if isinstance(node, BlockNode) :
> > parent_name_new = node.name
> > else :
> > parent_name_new = self.generate_name(node)
> >
> > bits.append("blocks_output['%s']=''" %
> > parent_name_new)
> > else :
> > parent_name_new = parent_name
> >
> > bits.append(self.compile_node(node, parent_name_new))
> > else:
> > bits.append(node)
> > return mark_safe(''.join([force_unicode(b) for b in bits]))
> >
> > Also this function is used for initialising blocks_output hashes for
> > uppermost nodes.
> >
> > 2. Blocks and dynamical inheritance
> > I have described before Node-derived classes improvements, but
> > django.template.loader_tags.BlockNode, django.template.loader_tags,
> > django.template.loader_tages.ExtendsNode and others have big influence
> > on compiled template architecture design, that's why I have allocated
> > whole section for them. Internal variables blocks_output,
> > blocks_output_sequence are used for providing support of dynamic
> > inheritance. They specify the way how template should be rendered, in
> > what sequence, what blocks should be outputted to user.
> > 'blocks_output' hash collects blocks or tags outputs, and
> > blocks_output_sequence saves sequence of block outputting. Because
> > parent template can contain its own sequence of blocks that is saved
> > in blocks_output_sequence, we must update the child
> > blocks_output_sequence without right of overriding it. Because parent
> > template can contain own blocks that is not present in child template,
> > we must update child blocks_output hash with not existing items in it.
> > By solving this requirements class BlockNode will be looked like :
> >
> > class BlockNode(Node):
> > def super_compile(self):
> > compiled_code = ["blocks_output['%s'] = blocks_output['%s'] +
> > blocks_output_parent['%s']" % (self.name,self.name,self.name)]
> > return compiled_code
> >
> > def compile(self,parent_name):
> > compiled_childs = self.nodelist.compile()
> > return compiled_childs
> >
> > blocks_output_parent – is blocks_output hash received from parent
> > template that contains cached parent output.
> > For providing support of static and dynamic inheritance class
> > ExtendsNode should have 'compile' method that will be looked like :
> >
> > class ExtendsNode(Node):
> > def get_parent_compiled(self):
> > if self.parent_name_expr: # dynamical inheritance
> > parent = self.parent_name_expr.compile()
> > if not parent :
> > if self.parent_name_expr:
> > error_msg = " Got this from the '%s' variable." %
> > self.parent_name_expr.token
> > raise TemplateSyntaxError(error_msg)
> > else : # static inheritance
> > parent = self.parent_name
> > if not parent:
> > error_msg = "Invalid template name in 'extends' tag:
> > %r." % parent
> > raise TemplateSyntaxError(error_msg)
> > if hasattr(parent, 'compile'):
> > return parent.compile() # parent is a Template object
> >
> > code = ["blocks_output, blocks_sequence_output_parent =
> > get_compiled_template(%s,no_output=True)" % self.parent_name,
> > "blocks_output_parent = blocks_output"]
> >
> > return code
> >
> > def compile(self,parent_name):
> > compiled_parent = self.get_compiled_parent()
> > return compiled_parent
> >
> > 3. Variables and filters modifications
> > Support of variables and filters are implemented by
> > django.template.FilterExpression class. It is wrapped by VariableNode
> > class – Node-derived class that implements simple call of
> > FilterExpression instance. For supporting compilation into python
> > bytecode VariableNode class should contain 'compile' function that
> > calls 'compile' method of FilterExpression instance :
> >
> > class VariableNode(Node):
> > def __compile__(self,parent_name):
> > return self.filter_expression.compile()
> >
> > And FilterExpression should have implemented 'compile' method in which
> > it calls filters compiled functions and variable resolving :
> >
> > class FilterExpression(object):
> > def compile(self, ignore_failures=False):
> > if isinstance(self.var, Variable):
> > obj =
> >
> self.var.compile(ignore_failures,settings.COMPILED_TEMPLATE_STRING_IF_INVALID)
> > else:
> > obj = self.var
> >
> > for func, args in self.filters:
> > arg_vals = ""
> > i = 0
> > for lookup, arg in args:
> > if i > 0 :
> > arg_vals = arg_vals + ","
> > if not lookup:
> > arg_vals = arg_vals + "\"%s\"" % mark_safe(arg)
> > else:
> > arg_vals = arg_vals + "%s" % arg.compile(context)
> >
> > if getattr(func, 'needs_autoescape', False):
> > new_obj = "%s(%s,autoescape=context.autoescape,%s)" %
> > (func.__name__,obj,arg_vals)
> > else:
> > new_obj = "%s(%s,%s)" % (func.__name__,obj,arg_vals)
> > if getattr(func, 'is_safe', False) and isinstance(obj,
> > SafeData):
> > obj = "mark_safe(%s)" % new_obj
> > elif isinstance(obj, EscapeData):
> > obj = "mark_for_escaping(%s)" % new_obj
> > else:
> > obj = new_obj
> > return [obj]
> >
> > By the way, 'compile' method FilterExpression has no parent_name param
> > – it is omitted because FilterExpression can't produce a block – it
> > can be called only from a block – VariableNode.
> > And django.template.Variable receives parameters ignore_failures, and
> > default value for variable :
> >
> > class Variable(object):
> > def compile(self,ignore_failures,default_value):
> > if self.lookups is not None:
> > value = "resolve(context,'%s',%s,'%s')" %
> > (self.lookups,ignore_failures,default_value)
> > else:
> > # We're dealing with a literal, so it's already been
> > "resolved"
> > value = "'%s'" % self.literal
> >
> > if self.translate:
> > value = "_(%s)" % value
> >
> > Using the Variable class introduced us to inner compiled template
> > function named 'resolve'. 'resolve' function implements finding entry
> > in context with looking name, value of entry can be simple value,
> > class, function. 'resolve' receives params :
> > 1.context – context variable that is passed to compiled template
> > 'render' method
> > 2.key – name of looking entry
> > 3.ignore_failures – if ignore_failures is true and finding entry is
> > failed, then it will return default_value or if it is omitted – None,
> > otherwise it will raise VariableDoesNotExist exception
> > 4.default_value – default value
> >
> > Compiled Template Architecture
> > Compiled template contains two sections :
> > 1.Dependencies section
> > 2.Compiled code section
> > Dependencies section is section that has default imports, and external
> > imports of user-written custom tags, filters. External imports are
> > determined by calling {% load %} tag.
> > Compiled code section is a section that contains template's python
> > byte code.
> > Structure of the compiled code :
> >
> > # -- DEPENDENCIES SECTION
> > # ----- Default imports
> > from django.template import Template, get_compiled_template
> > from django.template.defaultfilters import *
> > # ----- User custom imports
> > # from ... import ...
> > # -- END OF DEPENDENCIES SECTION
> >
> > def resolve(context,ignore_failures,default_value):
> > """ should be looked like method _resolve_lookup in
> > django.template.Variable """
> >
> > blocks_output = {}
> > blocks_output_sequence = []
> > blocks_sequence_output_parent = None
> >
> > def output():
> > for block_name in blocks_output_sequence :
> > print blocks_output.get(block_name,'')
> >
> > def render(context,no_output=False):
> > # START OF COMPILED TEMPLATE PYTHON BYTECODE
> >
> > # ...
> >
> > # END OF COMPILED TEMPLATE PYTHON BYTECODE
> > if blocks_sequence_output_parent is not None :
> > blocks_sequence_output = blocks_sequence_output_parent
> > if no_output :
> > return blocks_output, blocks_output_sequence
> > else :
> > output()
> >
> > Every compiled template has 3 functions :
> > resolve – discussed earlier
> > output – task of this function is to output blocks output cache in the
> > determined sequence
> > render – entrypoint of compiled template, that contains template's
> > compiled python bytecode. It supports one optional parameter –
> > 'no_output' – it is used only when called parent template – not to
> > output, to return cached data.
> >
> > Examples
> > Examples based on already written code :
> > compiling variables and filters :
> >
> > from django.template import Parser, FilterExpression
> >
> > token = 'variable|default:"Default value"|date:"Y-m-d"'
> > p = Parser('')
> > fe = FilterExpression(token, p)
> > fe.compile()
> > =>
> > 'date(default(resolve(context,variable,None,''),"Default value"),"Y-m-
> > d")'
> > template compilation sample :
> > child.html :
> > {% extends parent %}
> > {% block first_block %}
> > {% if True %}
> > some_info
> > {% endif %}
> > {% endblock %}
> > {% block second_block %}
> > {% endblock %}
> >
> > parent.html :
> > TextNode
> > {% block first_block %}
> > {% endblock %}
> >
> > =>
> > Compiled child template :
> > # -- DEPENDENCIES SECTION
> > # ----- Default imports
> > from django.template import Template, get_compiled_template
> > from django.template.defaultfilters import *
> > # ----- User custom imports
> > # from ... import ...
> > # -- END OF DEPENDENCIES SECTION
> >
> > def resolve(context,key,ignore_failures,default_value):
> > """ should be looked like method _resolve_lookup in
> > django.template.Variable """
> >
> >
> > blocks_output = {}
> > blocks_output_sequence = []
> > blocks_sequence_output_parent = None
> >
> > def output():
> > for block_name in blocks_output_sequence :
> > print blocks_output.get(block_name,'')
> >
> > def render(context,no_output=False):
> > blocks_output, blocks_sequence_output_parent =
> > get_compiled_template(resolve(context,'parent',None,''),no_output=True)
> > blocks_output_parent = blocks_output
> >
> > blocks_output['first_block'] = '' # entering block tag in NodeList
> > compile
> > blocks_sequence_output.append('first_block')
> > if True :
> > blocks_output['first_block'] = blocks_output['first_block'] +
> > "some_info"
> >
> > blocks_output['second_block'] = '' # entering block tag in
> > NodeList compile
> >
> > if blocks_sequence_output_parent is not None :
> > blocks_sequence_output = blocks_sequence_output_parent
> >
> > if no_output :
> > return blocks_output, blocks_output_sequence
> > else :
> > output()
> >
> > Compiled parent template :
> > # -- DEPENDENCIES SECTION
> > # ----- Default imports
> > from django.template import Template, get_compiled_template
> > from django.template.defaultfilters import *
> > # ----- User custom imports
> > # from ... import ...
> > # -- END OF DEPENDENCIES SECTION
> >
> > def resolve(context,key,ignore_failures,default_value):
> > """ should be looked like method _resolve_lookup in
> > django.template.Variable """
> >
> >
> > blocks_output = {}
> > blocks_output_sequence = []
> > blocks_sequence_output_parent = None
> >
> > def output():
> > for block_name in blocks_output_sequence :
> > print blocks_output.get(block_name,'')
> >
> > def render(context,no_output=False):
> > blocks_output['first_block'] = '' # entering block tag in NodeList
> > compile
> > blocks_sequence_output.append('first_block')
> >
> > if blocks_sequence_output_parent is not None :
> > blocks_sequence_output = blocks_sequence_output_parent
> >
> > if no_output :
> > return blocks_output, blocks_output_sequence
> > else :
> > output()
> >
> > Milestones
> > 1. Designing concept - current step
> > 2. Coding "template compilation" subsystem
> > 3. Writing unit-tests
> > 4. Optimization of written code
>
> A quick word of advice -- this timeline is probably enough to ensure
> that you *won't* get selected for the Django GSoC. It doesn't provide
> any evidence that you've done serious thought about how long the work
> involved will take. A serious proposal needs to have a work breakdown
> with 1-week granularity at the *most*. It should also give a clear
> mechanism for evaluating progress by indicating the deliverables at
> various stages of the project (e.g., after week 3, it will be possible
> to write a compiled template containing only {% if %} and {% block %}
> clauses).
>
> As an additional complication for your project -- you should take a
> look at your competition. You're proposing to work on a project that
> has also been proposed by Armin Ronacher. Armin has a long and
> demonstrated history of expertise when it comes to compiled template
> engines. If you're proposing a simliar project, then you would be well
> advised to clearly explain why:
> 1) your proposal is better than his, or
> 2) your proposal is a good compliment to his.
>
> Yours,
> Russ Magee %-)
>
> --
> You received this message because you are subscribed to the Google Groups
> "Django developers" group.
> To post to this group, send email to [email protected].
> To unsubscribe from this group, send email to
> [email protected].
> For more options, visit this group at
> http://groups.google.com/group/django-developers?hl=en.
>
>
--
You received this message because you are subscribed to the Google Groups
"Django developers" group.
To post to this group, send email to [email protected].
To unsubscribe from this group, send email to
[email protected].
For more options, visit this group at
http://groups.google.com/group/django-developers?hl=en.