Skip to content

Commit ea07aa1

Browse files
committed
Address PR feedback: rollback partial env, fail on bad templates
- TmpEnv.__enter__ rolls back already-set env vars if swap_env raises mid-loop, preventing leaked env state on enter failures - Remove silent TemplateError swallow: malformed templates now raise LookupError with the source string and error detail instead of silently passing through as literals - Convert tests to plain pytest classes (drop unittest.TestCase) - Add test_tmpenv_rollback_on_error for partial modification safety
1 parent 5fd61b2 commit ea07aa1

2 files changed

Lines changed: 25 additions & 9 deletions

File tree

src/seclab_taskflow_agent/env_utils.py

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -52,11 +52,9 @@ def swap_env(s: str, context: dict[str, Any] | None = None) -> str:
5252
}
5353
return template.render(**render_context)
5454
except jinja2.UndefinedError as e:
55-
# Convert Jinja undefined to LookupError for compatibility
5655
raise LookupError(str(e))
57-
except jinja2.TemplateError:
58-
# Not a template or failed to render, return as-is
59-
return s
56+
except jinja2.TemplateError as e:
57+
raise LookupError(f"Template rendering failed for: {s!r}: {e}")
6058

6159

6260
class TmpEnv:
@@ -69,8 +67,18 @@ def __init__(self, env: dict[str, str],
6967
self.restore_env = dict(os.environ)
7068

7169
def __enter__(self) -> None:
72-
for k, v in self.env.items():
73-
os.environ[k] = swap_env(v, self.context)
70+
applied: list[str] = []
71+
try:
72+
for k, v in self.env.items():
73+
os.environ[k] = swap_env(v, self.context)
74+
applied.append(k)
75+
except Exception:
76+
for k in applied:
77+
if k in self.restore_env:
78+
os.environ[k] = self.restore_env[k]
79+
else:
80+
os.environ.pop(k, None)
81+
raise
7482

7583
def __exit__(self, exc_type: type | None, exc_val: BaseException | None, exc_tb: Any | None) -> None:
7684
for k, v in self.env.items():

tests/test_env_utils.py

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,13 @@
44
"""Tests for env_utils: swap_env and TmpEnv with globals context."""
55

66
import os
7-
import unittest
87

98
import pytest
109

1110
from seclab_taskflow_agent.env_utils import TmpEnv, swap_env
1211

1312

14-
class TestSwapEnv(unittest.TestCase):
13+
class TestSwapEnv:
1514
"""Tests for swap_env template rendering."""
1615

1716
def test_plain_string_unchanged(self):
@@ -51,7 +50,7 @@ def test_no_context_backward_compat(self):
5150
assert swap_env("plain") == "plain"
5251

5352

54-
class TestTmpEnv(unittest.TestCase):
53+
class TestTmpEnv:
5554
"""Tests for TmpEnv context manager with globals."""
5655

5756
def test_globals_rendered_in_env_block(self):
@@ -77,3 +76,12 @@ def test_tmpenv_restores_original(self):
7776
assert os.environ["RESTORE_TEST"] == "overwritten"
7877
assert os.environ["RESTORE_TEST"] == "original"
7978
del os.environ["RESTORE_TEST"]
79+
80+
def test_tmpenv_rollback_on_error(self):
81+
"""Partial env modification is rolled back if swap_env raises."""
82+
env = {"GOOD_KEY": "value", "BAD_KEY": "{{ globals.missing }}"}
83+
with pytest.raises(LookupError):
84+
with TmpEnv(env):
85+
pass
86+
assert "GOOD_KEY" not in os.environ
87+
assert "BAD_KEY" not in os.environ

0 commit comments

Comments
 (0)