Source code for ibek.utils
"""
A class containing utility functions for passing into the Jinja context.
This allows us to provide simple functions that can be called inside
Jinja templates with {{ __utils__.function_name() }}. It also allows
us to maintain state between calls to the Jinja templates because
we pass a single instance of this class into all Jinja contexts.
"""
import os
from dataclasses import dataclass
from pathlib import Path
from typing import Any, Dict, Mapping
from jinja2 import Template
[docs]@dataclass
class Counter:
"""
Provides the ability to supply unique numbers to Jinja templates
"""
start: int
current: int
stop: int
def increment(self, count: int):
self.current += count
if self.current > self.stop:
raise ValueError(
f"Counter {self.current} exceeded stop value of {self.stop}"
)
[docs]class Utils:
"""
A Utility class for adding functions to the Jinja context
"""
def __init__(self: "Utils"):
self.file_name: str = ""
self.ioc_name: str = ""
self.__reset__()
def __reset__(self: "Utils"):
"""
Reset all saved state. For use in testing where more than one
IOC is rendered in a single session
"""
self.variables: Dict[str, Any] = {}
self.counters: Dict[str, Counter] = {}
[docs] def set_file_name(self: "Utils", file: Path):
"""
Set the ioc name based on the file name of the instance definition
"""
self.file_name = file.stem
[docs] def set_ioc_name(self: "Utils", name: str):
"""
Set the ioc name based on the file name of the instance definition
"""
self.ioc_name = name
[docs] def get_env(self, key: str) -> str:
"""
Get an environment variable
"""
return os.environ.get(key, "")
[docs] def set_var(self, key: str, value: Any):
"""create a global variable for our jinja context"""
self.variables[key] = value
[docs] def get_var(self, key: str) -> Any:
"""get the value a global variable for our jinja context"""
# Intentionally raises a KeyError if the key doesn't exist
return self.variables[key]
[docs] def counter(
self, name: str, start: int = 0, stop: int = 65535, inc: int = 1
) -> int:
"""
get a named counter that increments by inc each time it is called
creates a new counter if it does not yet exist
"""
counter = self.counters.get(name)
if counter is None:
counter = Counter(start, start, stop)
self.counters[name] = counter
else:
if counter.start != start or counter.stop != stop:
raise ValueError(
f"Redefining counter {name} with different start/stop values"
)
result = counter.current
counter.increment(inc)
self.counters[name] = counter
return result
[docs] def render(self, context: Any, template_text: str) -> str:
"""
Render a Jinja template with the global __utils__ object in the context
"""
jinja_template = Template(template_text)
return jinja_template.render(
context,
__utils__=self,
ioc_yaml_file_name=self.file_name,
ioc_name=self.ioc_name,
)
[docs] def render_map(self, context: Any, map: Mapping[str, str | None]) -> dict[str, str]:
"""
Render a map of jinja templates with values from the given context.
If given a key with a value of `None`, the key itself will be used as a template
value, so ``{"P": None}`` is equivalent to ``{"P": "{{ P }}"}``.
Args:
context: Context to extract template variables from
map: Map of macro to jinja template to render
"""
return {
key: self.render(
context, template if template is not None else "{{ %s }}" % key
)
for key, template in map.items()
}
# a singleton Utility object for sharing state across all Entity renders
UTILS: Utils = Utils()