Context¶
Every Jinja2 template and every provider hook receives a single merged context dictionary. This page explains where context values come from and how they are combined.
Sources and merge order¶
Context is assembled from four sources, applied in this order (later wins):
| # | Source | Where it is defined | Merge behaviour |
|---|---|---|---|
| 1 | Global context | Injected by repolish automatically | always present |
| 2 | Provider create_context() |
Each provider's module or Provider class |
shallow merge |
| 3 | Config context: |
repolish.yaml top-level context: key |
shallow - replaces each top-level key wholesale |
| 4 | Config context_overrides: |
Per-provider context_overrides: key |
deep - dot-notation paths patch nested fields |
The distinction between context: and context_overrides: matters when a
provider exposes a nested object (e.g. tools.uv.version). With context: you
must supply the whole top-level key; with context_overrides: you target only
the field you want to change.
Providers are loaded in the order listed under providers: in repolish.yaml.
Each provider's create_context() can read the merged context produced so far,
so later providers can react to earlier ones.
Global context¶
The repolish key is always present and is populated before any provider runs:
ctx.repolish.repo.owner # GitHub org / username inferred from git remote
ctx.repolish.repo.name # repository name
ctx.repolish.year # current calendar year (useful for license headers)
In templates:
Provider context¶
Each provider's create_context() method returns a typed Pydantic model.
from repolish import BaseContext, BaseInputs, Provider
class Ctx(BaseContext):
python_version: str = '3.11'
use_ruff: bool = True
class MyProvider(Provider[Ctx, BaseInputs]):
def create_context(self) -> Ctx:
return Ctx()
BaseContext already includes the repolish namespace, so typed providers get
ctx.repolish.repo.owner without any extra fields.
If you need to derive values from a prior provider's output, override
finalize_context() instead - it runs after all providers have emitted their
initial context and received any cross-provider inputs.
Config-level context¶
Values under context: in repolish.yaml are merged into the final context
using a shallow update. Each top-level key replaces whatever the provider
produced for that key in full. If a provider returns a nested object and you
only want to change one field inside it, you would have to repeat the entire
object under context: - which defeats the point.
For overriding a single nested field, use context_overrides: instead (see
below).
Context overrides per provider¶
context_overrides: under a provider entry applies changes using dot-notation
paths. This lets you reach into a nested object and patch exactly the field
you care about, without touching anything else:
providers:
my-provider:
cli: my-provider-link
context_overrides:
repolish.provider.python_version: '3.12' # patches one nested field
use_ruff: false # simple key also works
Nested dict form is also accepted and flattened automatically:
providers:
my-provider:
cli: my-provider-link
context_overrides:
my_provider:
some_flag: true
'tools.0.version': '0.8.0'
This is especially useful when the same provider is consumed with different settings in a monorepo, or when a provider namespace contains a deep structure that you want to patch without duplicating it.
What context is available in templates¶
All top-level keys from the merged context are available directly in Jinja2 templates:
The repolish namespace is always available:
Inspecting context after an apply¶
Every repolish apply run writes debug JSON files into .repolish/_/ so you
can see exactly what context each provider and each file received.
Per-provider: provider-context.<role>.<alias>.json¶
One file per provider, named after its session role and alias:
.repolish/_/provider-context.standalone.my-provider.json
.repolish/_/provider-context.root.devkit-workspace.json
.repolish/_/provider-context.pkg-alpha.devkit-python.json
Each file contains:
{
"alias": "my-provider",
"context": { ... }, // the full merged context this provider contributed
"files": [ // files this provider manages
{ "path": "pyproject.toml", "mode": "regular", "source": "pyproject.toml.jinja" }
]
}
In terminals that support hyperlinks, the provider name in the summary tree is a clickable link that opens this file directly.
Per-file: file-ctx/file-context.<slug>.json¶
One file per rendered template, named after the destination path (/ replaced
with --):
.repolish/_/file-ctx/file-context.pyproject.toml.json
.repolish/_/file-ctx/file-context.src--mylib--__init__.py.json
Each file records what was injected during rendering:
{
"dest": "pyproject.toml",
"owner": "my-provider",
"source_template": "pyproject.toml.jinja",
"provider_context_file": "provider-context.standalone.my-provider.json",
"extra_context": {} // any mapping-level extra_context on top of provider context
}
extra_context is populated when a TemplateMapping carries additional keys
that override or extend the provider context for that specific file only. If
extra_context is empty, the file was rendered purely from its provider's
context.
In terminals that support hyperlinks, each file name in the summary tree is a clickable link that opens this file directly.
Workflow¶
When a template renders unexpectedly, the typical workflow is:
- Run
repolish apply(orrepolish apply --check). - Click the file name in the summary tree to open its
file-contextJSON. - Check
extra_context- if it is non-empty, those keys shadowed the provider context. - Click
provider_context_filein that JSON to open the provider snapshot and inspect the fullcontextobject to see every key available in the template.