POC: Jinja + Markup middle-ground for outer repr template#8
Open
POC: Jinja + Markup middle-ground for outer repr template#8
Conversation
Routes the top-level repr through a single autoescape-enabled Jinja template and wraps existing formatter-produced HTML fragments in markupsafe.Markup at the boundary. Formatter internals (formatters.py, registry.py, components.py, sections.py, core.py) are untouched. The safety contract at the outer template: - plain-str values (container_id, depth, style) are autoescaped by default - Markup-wrapped fragments (header, sections, css, js, hints) pass through Adds jinja2>=3.1 and markupsafe>=3.0 to dependencies. Adds a minimal Environment module and one outer anndata.j2 template. The existing tests/visual_inspect_repr_html.py visual harness runs cleanly against this branch and produces the full 26-scenario comparison artifact. Repr test suite: 614 passed, 1 skipped — zero regressions.
95f421a to
2eef28f
Compare
3 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Proof-of-concept showing how a Jinja + Markup middle-ground can be layered onto the existing
_reprpipeline without touching formatter internals. Routes only the top-level repr through an autoescape-enabled Jinja template; wraps all existing formatter-produced fragments inmarkupsafe.Markupat the boundary.Full visual comparison
The existing
tests/visual_inspect_repr_html.pyharness (26 scenarios covering empty/minimal/view/dense/sparse/lazy/backed/nested AnnData, many-categories, custom formatters, TreeData, SpatialData, MuData, raw, unknown sections, serialization warnings, adversarial robustness, ecosystem extensibility, array-API devices) runs unchanged on this branch and produces the full comparison artifact:Regenerate locally with:
.venv/bin/python tests/visual_inspect_repr_html.py # → tests/repr_html_visual_test.htmlRendered output should be visually identical to the
html_repreference, because the same formatter code produces the same fragments; Jinja just concatenates them.Why
Context for the discussion on scverse#2236 around whether to adopt Jinja. The concrete question: what does a minimal middle-ground look like, and does it give a real safety uplift?
This branch is evidence that:
Markup/strtype distinction is load-bearing at the outer template. Autoescape closes the "forgot tohtml.escape()" class of bug for every direct insertion inanndata.j2(container_id,depth,style). Every trusted fragment is a singleMarkup(...)call, greppable at review time.formatters.py,registry.py,components.py,sections.py,core.pyare untouched.FormattedOutput,TypeFormatter,SectionFormatter,render_section,render_formatted_entrystill work as-is.register_formatter()still accepts the same signature;_repr_html_output formats remain backwards-compatible. If we later want to tightenpreview_html: strtopreview_html: Markup | str, that's a follow-up — not required by this POC.What changed
pyproject.tomljinja2>=3.1,markupsafe>=3.0src/anndata/_repr/environment.pyEnvironmentwith autoescape, loading fromanndata._repr.templatessrc/anndata/_repr/templates/anndata.j2src/anndata/_repr/html.pygenerate_repr_html()gathers existing fragments, wraps each inMarkup, callsenv.get_template('anndata.j2').render(...)Diff stats: 4 files, +100 / -43.
The trust contract
Markuppasses through{{ … }}verbatim.stris autoescaped. Forgetting would formerly be a latent XSS vector; under this contract it's structurally prevented at this layer.Minimal demonstration:
Attacker-controlled
container_idescaped to"><script>…by autoescape;Markup-wrapped trusted fragment passes through unchanged. Samerender()call.Scope — what this POC does not claim
ChoiceLoader/PackageLoaderwiring for third-party template contribution. That's a follow-up if ecosystem template authorship becomes the agreed direction.escape_html()calls in existing formatters. The safety gain is purely at the outer template layer.Takeaway
A Jinja +
Markupmigration can be incremental and low-cost at the entry point while leaving formatter internals and third-party integrations untouched. If the maintainers want to evaluate the middle-ground concretely, this branch is the smallest viable starting point; further conversion can proceed formatter-by-formatter from here.