Skip to content

Commit

Permalink
mypy: templite.py test_templite.py
Browse files Browse the repository at this point in the history
  • Loading branch information
nedbat committed Jan 8, 2023
1 parent 65aad08 commit 08564c0
Show file tree
Hide file tree
Showing 3 changed files with 87 additions and 72 deletions.
52 changes: 32 additions & 20 deletions coverage/templite.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,14 @@

# Coincidentally named the same as http://code.activestate.com/recipes/496702/

from __future__ import annotations

import re

from typing import (
Any, Callable, Dict, List, NoReturn, Optional, Set, Union, cast,
)


class TempliteSyntaxError(ValueError):
"""Raised when a template has a syntax error."""
Expand All @@ -26,45 +32,45 @@ class TempliteValueError(ValueError):
class CodeBuilder:
"""Build source code conveniently."""

def __init__(self, indent=0):
self.code = []
def __init__(self, indent: int = 0) -> None:
self.code: List[Union[str, CodeBuilder]] = []
self.indent_level = indent

def __str__(self):
def __str__(self) -> str:
return "".join(str(c) for c in self.code)

def add_line(self, line):
def add_line(self, line: str) -> None:
"""Add a line of source to the code.
Indentation and newline will be added for you, don't provide them.
"""
self.code.extend([" " * self.indent_level, line, "\n"])

def add_section(self):
def add_section(self) -> CodeBuilder:
"""Add a section, a sub-CodeBuilder."""
section = CodeBuilder(self.indent_level)
self.code.append(section)
return section

INDENT_STEP = 4 # PEP8 says so!

def indent(self):
def indent(self) -> None:
"""Increase the current indent for following lines."""
self.indent_level += self.INDENT_STEP

def dedent(self):
def dedent(self) -> None:
"""Decrease the current indent for following lines."""
self.indent_level -= self.INDENT_STEP

def get_globals(self):
def get_globals(self) -> Dict[str, Any]:
"""Execute the code, and return a dict of globals it defines."""
# A check that the caller really finished all the blocks they started.
assert self.indent_level == 0
# Get the Python source as a single string.
python_source = str(self)
# Execute the source, defining globals, and return them.
global_namespace = {}
global_namespace: Dict[str, Any] = {}
exec(python_source, global_namespace)
return global_namespace

Expand Down Expand Up @@ -111,7 +117,7 @@ class Templite:
})
"""
def __init__(self, text, *contexts):
def __init__(self, text: str, *contexts: Dict[str, Any]) -> None:
"""Construct a Templite with the given `text`.
`contexts` are dictionaries of values to use for future renderings.
Expand All @@ -122,8 +128,8 @@ def __init__(self, text, *contexts):
for context in contexts:
self.context.update(context)

self.all_vars = set()
self.loop_vars = set()
self.all_vars: Set[str] = set()
self.loop_vars: Set[str] = set()

# We construct a function in source form, then compile it and hold onto
# it, and execute it to render the template.
Expand All @@ -137,9 +143,9 @@ def __init__(self, text, *contexts):
code.add_line("extend_result = result.extend")
code.add_line("to_str = str")

buffered = []
buffered: List[str] = []

def flush_output():
def flush_output() -> None:
"""Force `buffered` to the code builder."""
if len(buffered) == 1:
code.add_line("append_result(%s)" % buffered[0])
Expand Down Expand Up @@ -232,9 +238,15 @@ def flush_output():

code.add_line('return "".join(result)')
code.dedent()
self._render_function = code.get_globals()['render_function']
self._render_function = cast(
Callable[
[Dict[str, Any], Callable[..., Any]],
str
],
code.get_globals()['render_function'],
)

def _expr_code(self, expr):
def _expr_code(self, expr: str) -> str:
"""Generate a Python expression for `expr`."""
if "|" in expr:
pipes = expr.split("|")
Expand All @@ -252,11 +264,11 @@ def _expr_code(self, expr):
code = "c_%s" % expr
return code

def _syntax_error(self, msg, thing):
def _syntax_error(self, msg: str, thing: Any) -> NoReturn:
"""Raise a syntax error using `msg`, and showing `thing`."""
raise TempliteSyntaxError(f"{msg}: {thing!r}")

def _variable(self, name, vars_set):
def _variable(self, name: str, vars_set: Set[str]) -> None:
"""Track that `name` is used as a variable.
Adds the name to `vars_set`, a set of variable names.
Expand All @@ -268,7 +280,7 @@ def _variable(self, name, vars_set):
self._syntax_error("Not a valid name", name)
vars_set.add(name)

def render(self, context=None):
def render(self, context: Optional[Dict[str, Any]] = None) -> str:
"""Render this template by applying it to `context`.
`context` is a dictionary of values to use in this rendering.
Expand All @@ -280,7 +292,7 @@ def render(self, context=None):
render_context.update(context)
return self._render_function(render_context, self._do_dots)

def _do_dots(self, value, *dots):
def _do_dots(self, value: Any, *dots: str) -> Any:
"""Evaluate dotted expressions at run-time."""
for dot in dots:
try:
Expand Down
Loading

0 comments on commit 08564c0

Please sign in to comment.