Extending tn-venv
The extension points are ordinary Python classes. This page shows the two most common ones — a new activator and a new seeder — and where to register them.
Writing an activator
An activator is a class with a name and a templates mapping of
file name → template text. Placeholders use the __VENV_*__ convention
and are substituted with shell-appropriate quoting by the base class.
# tn_venv/create/activators/elvish.py
from .base import Activator
ELVISH_ACTIVATE = """\
# generated by tn-venv
set-env VIRTUAL_ENV __VENV_DIR_SH__
set-env PATH "__VENV_BIN_PATH_SH__:"$E:PATH
"""
class ElvishActivator(Activator):
name = "elvish"
templates = {"activate.elv": ELVISH_ACTIVATE}
executable_names = ("activate.elv",) # chmod +x on POSIX
Available placeholders:
Placeholder |
Content |
Quoting |
|---|---|---|
|
environment directory |
raw |
|
folder name |
raw |
|
prompt text |
raw |
|
|
raw |
|
same, forward slashes |
raw |
|
environment python path |
raw |
|
shell-quoted forms |
POSIX sh |
|
PowerShell-quoted forms |
PowerShell |
|
fish-quoted forms |
fish |
If a shell needs a placeholder in a new quoting style, add a _xxx_quote
helper to Activator and expose a __VENV_*__XXX__ key — do not quote in
the subclass.
Register the class in tn_venv/create/activators/__init__.py by adding it
to the _ALL tuple; --activators elvish and the docs table pick it up
from there.
Writing a seeder
A seeder receives the finished CreatorContext and a Reporter, and
returns a SeedResult:
from tn_venv.seed.seeder import Seeder, SeedResult
from tn_venv.util.process import clean_pip_env, run_cmd
class UvSeeder(Seeder):
name = "uv"
def __init__(self, packages=None, **_ignored):
self.packages = packages or []
def seed(self, ctx, report):
if not self.packages:
return SeedResult(skipped=True)
run_cmd(
["uv", "pip", "install", "--python", str(ctx.env_exe), *self.packages],
env=clean_pip_env(),
report=report,
)
return SeedResult(packages=list(self.packages))
Register it in tn_venv/seed/seeder.py::make_seeder. Seeders must:
invoke the environment interpreter as
str(ctx.env_exe), never"python";use
clean_pip_env()(or an equivalent scrub) so the host’sPYTHONHOME/PYTHONPATHcannot leak in;raise
SeedErroron failure, with the failing command in the message.
Adding a new option
Options are declared once in tn_venv/config/spec.py:
OptionSpec(
"my_option", ("--my-option",),
kind="bool", default=False,
help="what it does",
)
That single declaration gives you the CLI flag (with --no-my-option for
booleans), the TN_VENV_MY_OPTION environment variable, the my-option
config-file key, and --dry-run output. The value arrives on
Options.my_option; consume it in session.run_session().
Testing your extension
Every built-in stage has a matching tests/test_*.py; follow
Testing. Activator tests assert on generated content and refuse
unsubstituted placeholders; seeder tests mock run_cmd so the suite never
touches the network.