6de61b965e
A new `template` value source evaluates a hardened, sandboxed Jinja expression over the live values of other value sources — the system's first float combinator. Backend: - Shared engine (utils/template_expr.py): ImmutableSandboxedEnvironment with filters/tests and auto-injected globals stripped; only min/max/abs/round/ clamp exposed; rejects **, string/collection-literal repetition, attribute access and non-global calls; NaN/inf-safe result coercion. - TemplateValueSource model + TemplateValueStream runtime: compile-once, primitives-only eval context, raw[name] exposure, eval_interval throttle, ref-counted input acquire/release, rename-safe hot-update. - Validation: unbound-variable + reserved-name rejection, reference cycle/depth guards (depth-only at create, full cycle at update), runtime acquire() depth backstop, and delete referential-integrity. - API: Create/Update/Response schemas + discriminated unions, _RESPONSE_MAP, and an advisory POST /value-sources/validate-template endpoint. - Demo seed: a static source plus a template combinator example. Frontend: - Editor modal section: repeatable inputs list (EntitySelect rows), a zero-dependency Jinja syntax highlighter, a hints/reference panel, and a debounced live validator that gates Save (stale-response-safe). - Graph editor: read-only template node with one edge per input. - i18n (en/ru/zh), icon, and card rendering. Tests: engine, stream, factory/cycle, validate endpoint, and demo seed.
51 lines
1.7 KiB
Python
51 lines
1.7 KiB
Python
"""Demo-seed regression tests (value sources, incl. the template combinator)."""
|
|
|
|
from ledgrab.core.demo_seed import seed_demo_data
|
|
from ledgrab.storage.database import Database
|
|
from ledgrab.storage.value_source import StaticValueSource, TemplateValueSource
|
|
from ledgrab.storage.value_source_store import ValueSourceStore
|
|
|
|
|
|
def _seed(tmp_path):
|
|
db = Database(tmp_path / "demo.db")
|
|
seed_demo_data(db)
|
|
return db
|
|
|
|
|
|
def test_demo_seeds_template_value_source(tmp_path):
|
|
db = _seed(tmp_path)
|
|
try:
|
|
store = ValueSourceStore(db)
|
|
by_id = {s.id: s for s in store.get_all_sources()}
|
|
|
|
base = by_id["vs_demo0001"]
|
|
boost = by_id["vs_demo0002"]
|
|
assert isinstance(base, StaticValueSource)
|
|
assert isinstance(boost, TemplateValueSource)
|
|
assert boost.template == "clamp(level * 1.5)"
|
|
assert boost.inputs == [{"name": "level", "value_source_id": "vs_demo0001"}]
|
|
|
|
# The reference graph is intact and consistent.
|
|
assert store.get_transitive_dependencies("vs_demo0002") == {"vs_demo0001"}
|
|
assert store.find_referencing_sources("vs_demo0001") == [boost.name]
|
|
finally:
|
|
db.close()
|
|
|
|
|
|
def test_demo_template_evaluates_through_manager(tmp_path):
|
|
"""The seeded template must actually evaluate over its seeded input."""
|
|
from ledgrab.core.processing.value_stream import ValueStreamManager
|
|
|
|
db = _seed(tmp_path)
|
|
try:
|
|
store = ValueSourceStore(db)
|
|
vsm = ValueStreamManager(value_source_store=store)
|
|
stream = vsm.acquire("vs_demo0002")
|
|
try:
|
|
# base level 0.5 -> clamp(0.5 * 1.5) = 0.75
|
|
assert abs(stream.get_value() - 0.75) < 1e-6
|
|
finally:
|
|
vsm.release("vs_demo0002")
|
|
finally:
|
|
db.close()
|