Skip to content

Commit

Permalink
Merge branch 'main' into 166-create-cicd-pipeline-for-testing-packagi…
Browse files Browse the repository at this point in the history
…ng-process-include-integration-tests
  • Loading branch information
SoloSynth1 committed Jun 21, 2024
2 parents a24fe26 + efea363 commit d249f6e
Show file tree
Hide file tree
Showing 3 changed files with 147 additions and 10 deletions.
71 changes: 65 additions & 6 deletions src/fixml/modules/template.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,33 @@
from jinja2 import Environment, PackageLoader, Template, meta
from os.path import getmtime
from typing import Union, Optional
from pathlib import Path

from jinja2 import (Environment, PackageLoader, Template, meta, BaseLoader,
TemplateNotFound)


class ExternalFileLoader(BaseLoader):
"""A dumber FileSystemLoader which does not accept path and thus allows
arbitrary file locations as valid template path."""
def __init__(self):
pass

def get_source(self, environment: Environment, path: Union[str, Path]):
resolved_path = Path(path).resolve()
if not resolved_path.is_file():
raise TemplateNotFound(path)
mtime = getmtime(path)
with open(path) as f:
source = f.read()
return source, resolved_path, lambda: mtime == getmtime(path)


class TemplateLoader:
"""Static wrapper class for setting states for Jinja2's Environment."""

template_exts = ["jinja", "j2"]
env = Environment(
loader=PackageLoader("fixml.data", "templates")
)
env = Environment(loader=PackageLoader("fixml.data", "templates"))
ext_env = Environment(loader=ExternalFileLoader())
template_aliases = {
"evaluation": "eval_report.md.jinja",
"checklist": "checklist.md.jinja",
Expand All @@ -22,6 +42,46 @@ def load(cls, template_name: str) -> Template:
candidates = [x for x in candidates if x is not None]
return cls.env.select_template(candidates)

@classmethod
def load_from_external(cls, ext_template_path: Union[str, Path],
validate_template: Optional[str] = None) -> Template:
"""Load template from external file.
Return the source as a Jinja2 Template when given a path.
Parameters
----------
ext_template_path : str or pathlib.Path
Path to the external template file.
validate_template : str, optional
If provided, the external template source will be compared with the
referred internal template to confirm all variables are present.
Returns
-------
jinja2.Template
Source file loaded as a Jinja2 Template.
"""
template = cls.ext_env.get_template(ext_template_path)
if validate_template:
source = cls.ext_env.loader.get_source(cls.ext_env,
ext_template_path)[0]
vars_external = cls._get_vars_from_source(source)
vars_internal = cls.list_vars_in_template(validate_template)
diff = set(vars_internal).difference(vars_external)
if diff:
raise ValueError(f"The external template does not contain all "
f"variables present in the internal template "
f"{validate_template}. Missing variables: "
f"{diff}")

return template

@classmethod
def _get_vars_from_source(cls, source: str) -> set[str]:
ast = cls.env.parse(source)
return meta.find_undeclared_variables(ast)

@classmethod
def list(cls) -> list[str]:
return cls.env.list_templates(extensions=cls.template_exts)
Expand All @@ -31,5 +91,4 @@ def list_vars_in_template(cls, template_name: str) -> set[str]:
"""List all variables present in template."""
template = cls.load(template_name)
source = cls.env.loader.get_source(cls.env, template.name)[0]
ast = cls.env.parse(source)
return meta.find_undeclared_variables(ast)
return cls._get_vars_from_source(source)
15 changes: 11 additions & 4 deletions src/fixml/modules/workflow/parse.py
Original file line number Diff line number Diff line change
@@ -1,23 +1,30 @@
from typing import Optional, Union
from pathlib import Path

import pandas as pd
import os

from ..template import TemplateLoader
from .response import EvaluationResponse
from ..mixins import ExportableMixin
from ..utils import get_extension


class ResponseParser(ExportableMixin):
def __init__(self, response: EvaluationResponse):
def __init__(self, response: EvaluationResponse,
export_template_path: Optional[Union[str, Path]] = None):
super().__init__()
self.response = response
self.evaluation_report = None
self.repository = self.response.repository.object
self.git_context = self.repository.git_context
self.items = []
self.export_template = TemplateLoader.load("evaluation")
if not export_template_path:
# use default template from package
self.export_template = TemplateLoader.load("evaluation")
else:
# load from external source, validate all vars are present
self.export_template = TemplateLoader.load_from_external(
export_template_path, "evaluation")

def _parse_items(self):
items = []
Expand Down Expand Up @@ -102,7 +109,7 @@ def get_completeness_score(self, score_format: str = 'fraction', verbose: bool =
def as_markdown(self, add_quarto_header: bool = False) -> str:

score = self.get_completeness_score(score_format='fraction')
summary_df = self.evaluation_report[['ID', 'Title', 'is_Satisfied', 'n_files_tested']]
summary_df = self.evaluation_report[['ID', 'Title', 'is_Satisfied']]

response = self.response
call_results = response.call_results
Expand Down
71 changes: 71 additions & 0 deletions tests/unit/test_template.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
from pathlib import Path
from importlib.resources import files

import pytest
from jinja2 import Template, TemplateNotFound
from fixml.modules.template import TemplateLoader


@pytest.mark.parametrize(
"template_name",
["evaluation", "checklist", "eval_report.md.jinja", "checklist.md.jinja"]
)
def test_template_can_load(template_name):
t = TemplateLoader.load(template_name)
assert type(t) == Template


@pytest.mark.parametrize(
"template_name",
["evaluation123" "eval_report.md",
"./src/fixml/data/templates/checklist.md.jinja"]
)
def test_errors_will_return_when_no_templates_can_be_found(template_name):
with pytest.raises(TemplateNotFound):
t = TemplateLoader.load(template_name)


def test_all_templates_can_be_listed():
template_data_dir = files("fixml.data.templates")
internal_templates = [x.name for x in template_data_dir.iterdir()]
assert internal_templates == TemplateLoader.list()


@pytest.mark.parametrize(
"template_path, template_name",
[
("./src/fixml/data/templates/checklist.md.jinja", "checklist"),
("./src/fixml/data/templates/eval_report.md.jinja", "evaluation"),
]
)
def test_templates_valid_themselves_when_loaded_externally(template_path,
template_name):
TemplateLoader.load_from_external(template_path,
validate_template=template_name)


@pytest.mark.parametrize(
"template_path, template_name",
[
("README.md", "checklist"),
("./src/fixml/data/templates/checklist.md.jinja", "evaluation"),
]
)
def test_external_templates_fail_validation_when_vars_dont_match(template_path,
template_name):
with pytest.raises(ValueError):
TemplateLoader.load_from_external(template_path,
validate_template=template_name)



@pytest.mark.parametrize(
"template_path",
[
("./src/fixml/data/templates/checklist.md.jinja"),
("./src/fixml/data/templates/eval_report.md.jinja"),
]
)
def test_external_template_have_full_file_path(template_path):
t = TemplateLoader.load_from_external(template_path)
assert t.filename == str(Path(template_path).resolve())

0 comments on commit d249f6e

Please sign in to comment.