Migrating providers to v1¶
This guide covers the two changes required to bring a pre-v1 provider up to
date: switching from module-style functions to a class-based Provider, and
updating templates to use plain Jinja2 variable names instead of the old
cookiecutter namespace.
1. Module-style functions → class-based Provider¶
Old providers exposed bare module-level functions. The v1 API uses a typed
Provider subclass instead.
Before:
# repolish.py (old module-style)
def create_context():
return {'shared_prefix': 'lib'}
def create_file_mappings(ctx):
return {'src/a.py': 'templates/mod.jinja'}
After:
# repolish.py (v1 class-based)
from repolish import BaseContext, BaseInputs, FileMode, Provider, TemplateMapping
class Ctx(BaseContext):
shared_prefix: str = 'lib'
license: str = 'MIT'
class MyProvider(Provider[Ctx, BaseInputs]):
def create_context(self) -> Ctx:
return Ctx()
def create_file_mappings(self, context: Ctx):
return {
'src/a.py': TemplateMapping('pkg_init.jinja', None),
'README.md': TemplateMapping('readme.jinja', None, file_mode=FileMode.CREATE_ONLY),
}
Use BaseContext (not a plain BaseModel) so the provider gets the built-in
repolish namespace (repolish.repo.owner, repolish.year, etc.) without any
extra fields.
Each repolish.py must contain exactly one Provider subclass. The loader will
find it automatically. If you import another provider class (e.g. a shared base)
at module level, declare __all__ listing only the intended class so the loader
knows which one to use.
2. Template namespace: drop cookiecutter.¶
Old templates accessed context values through a cookiecutter wrapper object:
In v1 the context keys are top-level Jinja2 variables — there is no
cookiecutter wrapper:
Rename every cookiecutter. reference across your template files. To catch
stragglers, run repolish lint after updating — it reports any undefined
variable references in your templates.
3. Symlinks: @resource_linker decorator → create_default_symlinks()¶
In v0.8, default symlinks were declared on the @resource_linker decorator that
powered the provider's link CLI:
# v0.8 — repolish.linker decorator
from repolish.linker import resource_linker, Symlink
@resource_linker(
library_name='myprovider',
default_symlinks=[
Symlink(source='configs/ruff.toml', target='ruff.toml'),
Symlink(source='configs/.editorconfig', target='.editorconfig'),
],
)
def main():
pass
In v1, symlinks are declared directly on the Provider class via
create_default_symlinks(). The Symlink dataclass is now imported from
repolish alongside the other provider types:
# v1 — Provider method
from repolish import BaseContext, BaseInputs, Provider, Symlink
class MyProvider(Provider[BaseContext, BaseInputs]):
def create_default_symlinks(self) -> list[Symlink]:
return [
Symlink(source='configs/ruff.toml', target='ruff.toml'),
Symlink(source='configs/.editorconfig', target='.editorconfig'),
]
The source path remains relative to the provider's resources_dir and
target remains relative to the project root — the semantics are identical.
Project-level overrides still work the same way in repolish.yaml:
providers:
myprovider:
symlinks: [] # skip all symlinks for this project
# or supply a custom list to replace the provider defaults
The @resource_linker decorator and repolish.linker module are still present
in v1 for building the provider's link CLI — only the place where default
symlinks are declared has moved into the Provider class.
Troubleshooting¶
Template references a key from another provider: move the value into this
provider's create_context(), or pass it via
TemplateMapping(...,
extra_context=...), or keep the template under the
provider that owns the context.
Multiple Provider subclasses in one module: define __all__ listing only
the intended class. The loader raises a clear error if it finds more than one
and __all__ is absent.