Architecture
tn-venv is a four-stage pipeline wrapped in a configuration system. Each
stage is a package with a narrow contract, and the stages never see each
other’s internals — they exchange two data structures: PythonInfo (the
probe of the base interpreter) and CreatorContext (the paths of the
environment being built).
┌────────────────────────── tn_venv.cli ──────────────────────────┐
│ argparse (driven by config/spec.py) → exit codes │
└──────────────────────────────┬──────────────────────────────────┘
│
tn_venv.config: CLI ◄ env vars ◄ config file ◄ defaults
│
tn_venv.session
(Options → run_session)
│
┌──────────────┬────────────────────────┼───────────────────────┬──────────────┐
▼ ▼ ▼ ▼ ▼
tn_venv. tn_venv.create tn_venv.create. tn_venv.seed tn_venv.report
discovery (Creator per platform) activators (Seeder) (leveled output)
│ │ │ │
PythonInfo ──▶ CreatorContext ──▶ activation scripts ──▶ pip/setuptools/…
Package map
Module |
Responsibility |
|---|---|
|
argument parsing, config layering, exit codes, |
|
the single source of truth for every option ( |
|
TOML/INI/env-var loading and coercion |
|
|
|
spec parsing, providers, probing, matching |
|
|
|
|
|
one module per shell, template-based |
|
|
|
inter-process file lock with stale detection |
|
subprocess wrapper with captured output |
|
leveled, colored console output |
|
the exception hierarchy |
Design rules
No runtime dependencies. Everything is stdlib. Optional behavior degrades instead of failing (e.g. a missing
pylauncher is a skipped provider, not an error).One source of truth per concern. Options exist once, in
OPTION_SPECS; platform branching exists once, in the creator factory; quoting rules exist once, in the activator base class.Probe, don’t guess. Interpreter facts (version, bitness, free-threading, launcher locations) come from executing the interpreter itself, never from parsing paths or filenames.
Mirror
venvsemantics. Where stdlibvenvhas an opinion — directory layout,pyvenv.cfgkeys, Windows launcher binaries,lib64symlinks — tn-venv adopts it, so environments stay interchangeable.Failures are typed. Every anticipated failure raises a subclass of
TNError; the CLI maps them to documented exit codes and the API re-raises them unmodified.
Data flow in detail
CLI parses flags with
default=Noneeverywhere, so “not supplied” is distinguishable from “supplied as false”.Config merges layers into a plain dict: defaults ← file ← env ← CLI.
Session turns the dict into a validated
Optionsdataclass and acquires the destination lock.Discovery resolves
--pythonspecs to a probedPythonInfo(subprocess JSON probe, cached).Creator builds the skeleton (
ensure_directories), installs the interpreter binaries (setup_python), writespyvenv.cfg(create_configuration) and the SCM ignore file.Activators render their templates against
CreatorContextwith shell-appropriate quoting.Seeder invokes the new interpreter with a scrubbed environment (
PYTHONHOME/PYTHONPATHremoved,VIRTUAL_ENVset) for ensurepip and all pip steps.The lock is released and a
SessionResultis returned; the CLI prints the activation hint.
Concurrency model
Creation is serialised per destination directory by
the file lock. Parallel
creations of different destinations are fully independent — tn-venv
holds no global mutable state beyond the per-process probe cache.