Skip to content

Commit

Permalink
Merge pull request #343 from 0hsn/feat/Implement-Jinja3
Browse files Browse the repository at this point in the history
Feat: Implement jinja2 for variable management
  • Loading branch information
0hsn authored Dec 5, 2024
2 parents 1699b10 + 174e15c commit ea68576
Show file tree
Hide file tree
Showing 13 changed files with 161 additions and 323 deletions.
8 changes: 4 additions & 4 deletions .github/workflows/coverage-ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,11 @@ jobs:
coverage:
name: Runs on ubuntu-latest with 3.11.0
runs-on: ubuntu-latest
if: ${{ ! github.event.pull_request.draft }}

env:
COVERALLS_REPO_TOKEN: ${{ secrets.COVERALLS_REPO_TOKEN }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
COINSTATS_API_KEY: ${{ secrets.COINSTATS_API_KEY }}

steps:
- name: Checkout code
Expand All @@ -23,14 +23,14 @@ jobs:
- name: Set up Python 3.11
uses: actions/setup-python@v5
with:
python-version: '3.11'
cache: 'pip'
python-version: "3.11"
cache: "pip"

- name: Install dependencies
run: pip install -r requirements-dev.txt

- name: Run tests and generate .coverage
run: python -m pytest --cov=./chk
run: py.test -s --ignore=chk --cov=./chk

- name: Upload to coveralls.io
run: coveralls
14 changes: 10 additions & 4 deletions .github/workflows/test-ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,12 @@ on:
jobs:
test:
name: Run test on ${{ matrix.os }} with ${{ matrix.py_ver }}

env:
COVERALLS_REPO_TOKEN: ${{ secrets.COVERALLS_REPO_TOKEN }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
COINSTATS_API_KEY: ${{ secrets.COINSTATS_API_KEY }}

strategy:
matrix:
os:
Expand All @@ -19,7 +25,7 @@ jobs:
- windows-latest
- windows-2022
- windows-2019

runs-on: ${{ matrix.os }}
if: ${{ ! github.event.pull_request.draft }}

Expand All @@ -30,11 +36,11 @@ jobs:
- name: Set up Python 3.11
uses: actions/setup-python@v5
with:
python-version: '3.11'
cache: 'pip'
python-version: "3.11"
cache: "pip"

- name: Install dependencies
run: pip install -r requirements-dev.txt

- name: Run tests
run: python -m pytest
run: py.test -s --ignore=chk
1 change: 1 addition & 0 deletions Pipfile
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ xmltodict = "*"
python-dotenv = "*"
pydantic = "*"
loguru = "*"
jinja2 = "*"

[dev-packages]
pytest = "*"
Expand Down
78 changes: 77 additions & 1 deletion Pipfile.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 5 additions & 5 deletions chk/infrastructure/symbol_table.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
from chk.infrastructure.document import VersionedDocument, VersionedDocumentV2
from chk.infrastructure.file_loader import ExecuteContext, FileContext
from chk.infrastructure.helper import data_get
from chk.infrastructure.templating import StrTemplate
from chk.infrastructure.templating import JinjaTemplate, is_template_str


class VariableConfigNode(enum.StrEnum):
Expand Down Expand Up @@ -95,8 +95,8 @@ def replace_value(doc: dict | list, var_s: dict) -> dict | list:

for key, val in list(doc.items() if isinstance(doc, dict) else enumerate(doc)):
if isinstance(val, str):
str_tpl = StrTemplate(val)
doc[key] = str_tpl.substitute(var_s)
str_tpl = JinjaTemplate.make(val)
doc[key] = str_tpl.render(var_s)
elif isinstance(val, (dict, list)):
doc[key] = replace_value(doc[key], var_s)
return doc
Expand Down Expand Up @@ -143,7 +143,7 @@ def handle_absolute(cls, variable_doc: Variables, document: dict) -> None:
"""

for key, val in document.items():
if isinstance(val, str) and StrTemplate.is_tpl(val):
if isinstance(val, str) and is_template_str(val):
continue

variable_doc[key] = val
Expand All @@ -165,7 +165,7 @@ def handle_composite(

composite_values = {}
for key, val in document.items():
if isinstance(val, str) and StrTemplate.is_tpl(val):
if isinstance(val, str) and is_template_str(val):
composite_values[key] = val

if composite_values:
Expand Down
151 changes: 25 additions & 126 deletions chk/infrastructure/templating.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,139 +2,38 @@
Templating module
"""

import typing
import re
from jinja2.environment import Template
from jinja2.nativetypes import NativeEnvironment

from chk.infrastructure.logging import error

class StrTemplate:
"""
class to replace variables given in between <%, and %>.
Supports value from dictionary and list.
"""

d_start = "<%"
d_end = "%>"

def __init__(self, templated_string: str = "") -> None:
"""StrTemplate Constructor
Args:
templated_string: string to set, "" is default
"""
if not isinstance(templated_string, str):
raise ValueError("Only string allowed in template.")

self.template = templated_string
# (<%\s*[\'\"\(\)|a-zA-Z0-9_.]+\s*%>)

def substitute(
self, mapping: dict | None = None, /, **keywords: dict
) -> typing.Any:
"""Substitute values from mapping and keywords"""

if not mapping:
mapping = {}

if not isinstance(mapping, dict):
raise ValueError("Only mapping allowed in mapping.")

if not (
StrTemplate.d_start in self.template and StrTemplate.d_end in self.template
):
return self.template

return self._replace(self.template, {**mapping, **keywords})
class JinjaTemplate:
"""JinjaTemplate is wrapper class for JinjaNativeTemplate"""

@staticmethod
def _parse(container: str) -> list[str]:
"""replace values found in string with typed return
Args:
container: str
Returns:
list: list of parsed object
"""

if not isinstance(container, str):
return []

line_split = re.split(
"("
+ StrTemplate.d_start
+ r"\s*[a-zA-Z0-9_.]+\s*"
+ StrTemplate.d_end
+ ")",
container,
def make(template: str) -> Template:
"""Create a NativeEnvironment with default settings"""

if not template or not isinstance(template, str):
e_msg = f"Malformed template: {template}"
error(e_msg)
raise ValueError(e_msg)

n_env = NativeEnvironment(
variable_start_string="<%",
variable_end_string="%>",
block_start_string="<@",
block_end_string="@>",
comment_start_string="<#",
comment_end_string="#>",
)

return [item for item in line_split if item]

@staticmethod
def _replace(container: str, replace_with: dict) -> typing.Any:
"""replace values found in string with typed return
Args:
container: str
replace_with: dict
Returns:
object: object found in replace_with
"""
return n_env.from_string(template)

if len(replace_with) == 0:
return container

if not (line_strip := StrTemplate._parse(container)):
return container

if (
len(line_strip) == 1
and container in line_strip
and StrTemplate.d_start not in container
and StrTemplate.d_end not in container
):
return container

final_list_strip: list[object] = []

for item in line_strip:
if StrTemplate.d_start in item and StrTemplate.d_end in item:
value = StrTemplate._get(replace_with, item.strip(" <>%"), None)

final_list_strip.append(value or item)
else:
final_list_strip.append(item)

return (
"".join([str(li) for li in final_list_strip])
if len(final_list_strip) > 1
else final_list_strip.pop()
)

@staticmethod
def _get(var: dict | list, keymap: str, default: object = None) -> typing.Any:
"""
Get a value of a dict|list by dot notation key
:param var: the dict|list we'll get value for
:param keymap: dot separated keys
:param default: None
:return:
"""

data = var.copy()
indexes = keymap.split(".")

for index in indexes:
if isinstance(data, dict) and index in data:
data = data[index]
elif isinstance(data, list) and index.isnumeric():
data = data[int(index)]
else:
return default

return data

@staticmethod
def is_tpl(tpl_str: str) -> bool:
"""Check given string is templated string or not"""
def is_template_str(tpl: str) -> bool:
"""Check given string is templated string or not"""

return StrTemplate.d_start in tpl_str and StrTemplate.d_end in tpl_str
_dm_sets = [("<%", "%>"), ("<@", "@>"), ("<#", "#>")]
return any([_dm_set[0] in tpl and _dm_set[1] in tpl for _dm_set in _dm_sets])
2 changes: 1 addition & 1 deletion chk/modules/fetch/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ def call(file_ctx: FileContext, exec_ctx: ExecuteContext) -> ExecResponse:
debug(variable_doc.data)

HttpDocumentSupport.process_request_template(http_doc, variable_doc)
debug(http_doc.model_dump_json())
debug(http_doc.model_dump())

# try:
response = HttpDocumentSupport.execute_request(http_doc)
Expand Down
Loading

0 comments on commit ea68576

Please sign in to comment.