|
12 | 12 |
|
13 | 13 |
|
14 | 14 |
|
15 | | -def swap_env(s: str) -> str: |
16 | | - """Replace {{ env('VAR') }} patterns in string with environment values. |
| 15 | +def swap_env(s: str, context: dict[str, Any] | None = None) -> str: |
| 16 | + """Render Jinja template expressions in a string. |
| 17 | +
|
| 18 | + Supports expressions such as ``{{ env('VAR') }}``. Template variables |
| 19 | + like ``{{ globals.X }}`` are only available when provided by the caller |
| 20 | + via ``context`` (e.g. ``{'globals': {...}}``). |
17 | 21 |
|
18 | 22 | Args: |
19 | | - s: String potentially containing env templates |
| 23 | + s: String potentially containing templates. |
| 24 | + context: Optional template context. Variables such as ``globals`` |
| 25 | + must be supplied here to be available during rendering. |
20 | 26 |
|
21 | 27 | Returns: |
22 | | - String with env templates replaced |
| 28 | + String with templates replaced. |
23 | 29 |
|
24 | 30 | Raises: |
25 | | - LookupError: If required env var not found |
| 31 | + LookupError: If a required environment variable or template |
| 32 | + variable is not found during rendering. |
26 | 33 | """ |
27 | | - # Quick check if templating needed |
28 | | - if '{{' not in s: |
29 | | - return s |
30 | | - |
31 | 34 | try: |
32 | | - # Import here to avoid circular dependency |
33 | 35 | from .template_utils import create_jinja_environment |
34 | 36 | from .available_tools import AvailableTools |
35 | 37 |
|
36 | 38 | available_tools = AvailableTools() |
37 | 39 | jinja_env = create_jinja_environment(available_tools) |
38 | 40 | template = jinja_env.from_string(s) |
39 | | - return template.render() |
| 41 | + # Filter out keys that collide with built-in template globals |
| 42 | + # (e.g. the env() helper) to prevent callers from breaking them. |
| 43 | + reserved_keys = set(jinja_env.globals) |
| 44 | + render_context = { |
| 45 | + key: value for key, value in (context or {}).items() |
| 46 | + if key not in reserved_keys |
| 47 | + } |
| 48 | + return template.render(**render_context) |
40 | 49 | except jinja2.UndefinedError as e: |
41 | | - # Convert Jinja undefined to LookupError for compatibility |
42 | 50 | raise LookupError(str(e)) |
43 | | - except jinja2.TemplateError: |
44 | | - # Not a template or failed to render, return as-is |
45 | | - return s |
| 51 | + except jinja2.TemplateError as e: |
| 52 | + raise LookupError(f"Template rendering failed for: {s!r}: {e}") |
46 | 53 |
|
47 | 54 |
|
48 | 55 | class TmpEnv: |
49 | 56 | """Context manager that temporarily sets environment variables.""" |
50 | 57 |
|
51 | | - def __init__(self, env: dict[str, str]) -> None: |
| 58 | + def __init__(self, env: dict[str, str], |
| 59 | + context: dict[str, Any] | None = None) -> None: |
52 | 60 | self.env = dict(env) |
| 61 | + self.context = context |
53 | 62 | self.restore_env = dict(os.environ) |
54 | 63 |
|
55 | 64 | def __enter__(self) -> None: |
56 | | - for k, v in self.env.items(): |
57 | | - os.environ[k] = swap_env(v) |
| 65 | + applied: list[str] = [] |
| 66 | + try: |
| 67 | + for k, v in self.env.items(): |
| 68 | + os.environ[k] = swap_env(v, self.context) |
| 69 | + applied.append(k) |
| 70 | + except Exception: |
| 71 | + for k in applied: |
| 72 | + if k in self.restore_env: |
| 73 | + os.environ[k] = self.restore_env[k] |
| 74 | + else: |
| 75 | + os.environ.pop(k, None) |
| 76 | + raise |
58 | 77 |
|
59 | 78 | def __exit__(self, exc_type: type | None, exc_val: BaseException | None, exc_tb: Any | None) -> None: |
60 | 79 | for k, v in self.env.items(): |
|
0 commit comments