Categories We Write About

Build a Python-based template engine

Creating a Python-based template engine involves building a system that can take a template string containing placeholders or tags and replace those placeholders dynamically with provided data. Below is a detailed example of a simple yet functional template engine supporting variable substitution, basic loops, and conditionals.

python
import re class TemplateEngine: VARIABLE_PATTERN = re.compile(r'{{s*(w+)s*}}') BLOCK_PATTERN = re.compile(r'{%(.+?)%}') def __init__(self, template_str): self.template_str = template_str.strip() def render(self, context): tokens = self._tokenize(self.template_str) output = self._render_tokens(tokens, context) return output def _tokenize(self, template): """ Break template into tokens of text, variables, and blocks """ tokens = [] pos = 0 while pos < len(template): var_match = self.VARIABLE_PATTERN.search(template, pos) block_match = self.BLOCK_PATTERN.search(template, pos) # Find closest match nearest = None if var_match and block_match: if var_match.start() < block_match.start(): nearest = ('var', var_match) else: nearest = ('block', block_match) elif var_match: nearest = ('var', var_match) elif block_match: nearest = ('block', block_match) else: break start = nearest[1].start() end = nearest[1].end() # Add preceding text token if start > pos: tokens.append(('text', template[pos:start])) if nearest[0] == 'var': tokens.append(('var', nearest[1].group(1).strip())) else: tokens.append(('block', nearest[1].group(1).strip())) pos = end # Add remaining text if pos < len(template): tokens.append(('text', template[pos:])) return tokens def _render_tokens(self, tokens, context): output = '' tokens = tokens.copy() while tokens: token_type, token_value = tokens.pop(0) if token_type == 'text': output += token_value elif token_type == 'var': output += str(self._resolve_variable(token_value, context)) elif token_type == 'block': parts = token_value.split() if parts[0] == 'if': condition = ' '.join(parts[1:]) inner_tokens, tokens = self._collect_until(tokens, 'endif') if self._eval_condition(condition, context): output += self._render_tokens(inner_tokens, context) elif parts[0] == 'for': # {% for item in items %} var_name = parts[1] iterable_name = parts[3] inner_tokens, tokens = self._collect_until(tokens, 'endfor') iterable = self._resolve_variable(iterable_name, context) if iterable: for item in iterable: new_context = context.copy() new_context[var_name] = item output += self._render_tokens(inner_tokens, new_context) # Can extend for elif, else etc. return output def _collect_until(self, tokens, end_tag): collected = [] depth = 0 while tokens: token_type, token_value = tokens.pop(0) if token_type == 'block': parts = token_value.split() if parts[0] == end_tag and depth == 0: return collected, tokens elif parts[0] in ['if', 'for']: depth += 1 elif parts[0] in ['endif', 'endfor']: depth -= 1 collected.append((token_type, token_value)) raise ValueError(f"Missing {% {end_tag} %} tag") def _resolve_variable(self, var_name, context): # Support dot notation: user.name parts = var_name.split('.') val = context try: for part in parts: if isinstance(val, dict): val = val[part] else: val = getattr(val, part) return val except (KeyError, AttributeError): return '' def _eval_condition(self, condition, context): # VERY basic condition evaluator (supports variable truthiness and equality) # Example conditions: "user.is_active", "count == 5" tokens = condition.split() if len(tokens) == 1: # Single variable truthiness val = self._resolve_variable(tokens[0], context) return bool(val) elif len(tokens) == 3: left = self._resolve_variable(tokens[0], context) op = tokens[1] right = tokens[2].strip("'"") if op == '==': return str(left) == right elif op == '!=': return str(left) != right # Extend with more operators if needed else: return False # Example usage: template = """ Hello, {{ user.name }}! {% if user.is_active %} Your account is active. {% endif %} Your favorite colors are: {% for color in user.favorites.colors %} - {{ color }} {% endfor %} """ context = { 'user': { 'name': 'Alice', 'is_active': True, 'favorites': { 'colors': ['red', 'green', 'blue'] } } } engine = TemplateEngine(template) print(engine.render(context))

This engine features:

  • Variable substitution with {{ var }}, supporting dot notation like user.name.

  • Basic {% if condition %} ... {% endif %} blocks.

  • Simple {% for var in iterable %} ... {% endfor %} loops.

  • Context variable lookups including nested dictionaries and object attributes.

  • Very basic condition evaluation (truthiness and simple equality).

You can extend this by adding support for else, elif, more operators, filters, and caching parsed templates for performance. This serves as a solid foundational template engine in pure Python.

Share This Page:

Enter your email below to join The Palos Publishing Company Email List

We respect your email privacy

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *

Categories We Write About