Skip to content

fix(loader): preserve non-env {{...}} placeholders in ConfigLoader (#81) - #132

Open
owen-pengtao wants to merge 1 commit into
contextforge-org:0.1.xfrom
owen-pengtao:fix/configloader-preserve-nonenv-placeholders
Open

owen-pengtao wants to merge 1 commit into
contextforge-org:0.1.xfrom
owen-pengtao:fix/configloader-preserve-nonenv-placeholders

Conversation

@owen-pengtao

Copy link
Copy Markdown

Summary

Fixes #81ConfigLoader.load_config blanks non-env {{...}} placeholders in the plugin config.

load_config renders the entire config.yaml through one jinja pass with the default (empty) Undefined:

jinja_env = SandboxedEnvironment(loader=jinja2.BaseLoader(), autoescape=True)
rendered_template = jinja_env.from_string(template).render(env=os.environ)

Because the render only defines env, any {{...}} that is not an env reference is silently rendered to "" at load time — including placeholders a plugin legitimately stores in its config to render itself later, at runtime. The reported symptom: WebhookNotification's default_template

{ "event": "{{event}}", "timestamp": "{{timestamp}}", "violation": {{violation}}, "metadata": {{metadata}} }

is blanked to { "event": "", "timestamp": "", "violation": , ... } at load, so the plugin posts an all-empty, invalid-JSON body.

Fix

Render with jinja2.DebugUndefined (issue #81's suggested option 1) so undefined names — everything except the env.* values passed to render() — survive verbatim as {{ name }} instead of being blanked, while env.* references are still substituted.

jinja_env = SandboxedEnvironment(loader=jinja2.BaseLoader(), autoescape=True, undefined=jinja2.DebugUndefined)

Behavior note

A bare {{ env.MISSING }} for an unset env var now renders literally as {{ env.MISSING }} instead of "". Configs that want an empty fallback should use the default filter ({{ env.X | default('') }}), which continues to work. This is the documented trade-off of the DebugUndefined approach.

Tests

Adds tests/unit/cpex/framework/loader/test_config_loader.py:

Verified against the real 0.1.x ConfigLoader: current behavior blanks the placeholders; with this change the three tests pass and the WebhookNotification-style default_template survives load.

Signed-off-by: Owen Peng tao.peng@tibco.com

…ontextforge-org#81)

ConfigLoader.load_config renders the whole plugin config.yaml through a single
jinja pass with the default (empty) Undefined, so any {{...}} that is not an env
reference is silently blanked to "". A plugin that stores a runtime template in
its config (e.g. WebhookNotification's default_template) is therefore emptied at
load time and later emits an all-empty, invalid body.

Render with jinja2.DebugUndefined so undefined names (everything except the
env.* values passed to render()) survive verbatim as {{ name }} instead of being
blanked, while env.* references are still substituted. Adds regression tests
covering env substitution, non-env placeholder preservation, and use_jinja=False.

Fixes contextforge-org#81.

Signed-off-by: Owen Peng <tao.peng@tibco.com>
@owen-pengtao

Copy link
Copy Markdown
Author

can anyone review this pr?

@araujof

araujof commented Aug 17, 2026

Copy link
Copy Markdown
Contributor

can anyone review this pr?

@owen-pengtao I was busy with something else, but marking myself to review it this week.

@araujof araujof self-assigned this Aug 17, 2026
@owen-pengtao

Copy link
Copy Markdown
Author

@araujof do you able to review this pr week?

@araujof araujof added the 0.1.x label Sep 18, 2026
@araujof araujof added this to CPEX Sep 18, 2026
@github-project-automation github-project-automation Bot moved this to Backlog in CPEX Sep 18, 2026
@araujof araujof added this to the 0.1.4 milestone Sep 18, 2026
@araujof araujof moved this from Backlog to In review in CPEX Sep 18, 2026

@araujof araujof left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@owen-pengtao Thanks for this PR. DebugUndefined does not preserve arbitrary non-env Jinja.** The entire YAML file is still evaluated as a Jinja template. For example, {{ event.name }} raises UndefinedError, filters can transform placeholders, and conditional blocks can disappear. An unset {{ env.MISSING }} also becomes Jinja diagnostic text rather than remaining {{ env.MISSING }} as described in the PR. Use genuinely env-only interpolation and add regression coverage for nested expressions, filters, conditionals, and missing environment variables.

Below is a suggested alternative that addresses this problem. Would you like to try it and update your PR?

Suggested implementation

Replace only the explicitly supported {{ env.NAME }} syntax instead of rendering the entire configuration as Jinja. For example:

import re

_ENV_REFERENCE = re.compile(r"{{\s*env\.([A-Za-z_][A-Za-z0-9_]*)\s*}}")


def _interpolate_env(template: str) -> str:
    def replace(match: re.Match[str]) -> str:
        name = match.group(1)
        # Preserve an unset reference. This policy could instead raise a clear
        # configuration error, but it should be explicit and tested.
        return os.environ.get(name, match.group(0))

    return _ENV_REFERENCE.sub(replace, template)

Then use _interpolate_env(template) in the use_jinja branch. Given:

endpoint: "{{ env.WEBHOOK_URL }}"
default_template: "{{ event.name | upper }}"

only WEBHOOK_URL would be substituted; the runtime template would remain byte-for-byte unchanged. If | default(...) must remain supported, add it as an explicitly parsed part of this restricted syntax rather than evaluating the whole YAML document with Jinja.

Add tests confirming that nested expressions, filters, and {% ... %} blocks remain unchanged, plus a test defining the behavior of an unset environment variable.

@araujof araujof removed this from the 0.1.4 milestone Sep 18, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

Status: In review

Development

Successfully merging this pull request may close these issues.

2 participants