from typing import Optional, Any, Dict, List
import os
from jinja2 import FileSystemLoader
from pyexlatex.models.jinja import JinjaTemplate, JinjaEnvironment, DataString
from pyexlatex.models.template import Template
[docs]class Model(Template):
"""
A data model combined with a Jinja template.
Passes any class variables and methods to template to render the output string.
Subclass this class to create your own data models for templates.
Set exclude_attrs class variable as a list of strings of any attributes which should not be
passed to the template.
Examples:
>>> import pyexlatex as pl
>>> template_str = "{{ my_var | Bold }} {{ my_link }} {{ my_text }}"
>>>
>>> class MyModel(pl.Model):
>>> my_var = 'woo'
>>> my_link = pl.Hyperlink('https://www.google.com', 'Link!!!!')
>>>
>>> def __init__(self, my_text: str, *args, **kwargs):
>>> self.my_text = my_text
>>> super().__init__(*args, **kwargs)
>>>
>>> model = MyModel('stuff', template_str=template_str)
>>> str(model)
'\\textbf{woo} \\href{https://www.google.com}{Link!!!!} stuff '
"""
# Additional attributes to exclude adding to template variables
exclude_attrs: List[str] = []
[docs] def __init__(self, template_str: Optional[str] = None, template_path: Optional[str] = None,
environment: Optional[JinjaEnvironment] = None):
self.template_str = template_str
self.template_path = template_path
self._validate_template()
self.environment = environment
self.contents = self._get_contents()
super().__init__()
@property
def environment(self):
return self._environment
@environment.setter
def environment(self, env: Optional[JinjaEnvironment]):
self._active_template_path = self.template_path
if env is not None:
self._environment = env
return
if self.template_path is not None:
template_dir = os.path.dirname(os.path.realpath(self.template_path))
template_name = os.path.basename(self.template_path)
# Create environment with file system loader in the folder containing the template
self._environment = JinjaEnvironment(loader=FileSystemLoader(template_dir))
# Switch template path str passed to environment to just name, as environment was created in that folder
self._active_template_path = template_name
if self.template_str is not None:
self._environment = JinjaEnvironment()
@property
def template(self) -> JinjaTemplate:
if self.template_str is not None:
return self.environment.from_string(self.template_str)
# Get from path
return self.environment.get_template(self._active_template_path)
@property
def render_dict(self) -> Dict[str, Any]:
always_exclude_attrs = [
'environment',
'template',
'template_str',
'template_path',
'render_dict',
'contents',
'format_contents',
'add_data_from_content',
'init_data',
'data',
'add_package',
'equal_attrs',
'exclude_attrs',
'is_DocumentItem',
'is_LatexItem',
'join',
'name',
'next_level_down_class'
]
full_exclude = always_exclude_attrs + self.exclude_attrs
attrs = [item for item in dir(self) if item not in full_exclude and not item.startswith('_')]
return {attr: getattr(self, attr) for attr in attrs}
def _validate_template(self):
if self.template_str is not None and self.template_path is not None:
raise ValueError(f'only specify one of template_str, template_path. got {self.template_str} for '
f'template_str, {self.template_path} for template_path')
if self.template_str is None and self.template_path is None:
raise ValueError(f'must specify one of template_str, template_path. got {self.template_str} for '
f'template_str, {self.template_path} for template_path')
def _get_contents(self) -> DataString:
render_dict = self.render_dict
self.add_data_from_content(render_dict)
return self.template.render(**render_dict)