pyro is the shared uv/Python plumbing for the fyre R-package
ecosystem (reportifyr and presentifyr). It wraps the
uv CLI to install uv itself, if
missing, manage a project-local pyproject.toml, and materialize a
.venv/ pinned by uv.lock.
It is designed to be called from sibling fyr-packages (which forward
their initialize_*() to pyro), but it can also be used directly
in any R project that wants reproducible Python deps.
pak::pkg_install("a2-ai/pyro")pyro treats the project’s own pyproject.toml as the source
of truth for Python dependencies. pyro never owns or rewrites the
user’s pins.
- Seed (first init only). If the project has no
pyproject.toml,initialize_python()seeds one frompyro’s bundled reference spec (inst/extdata/pyproject.toml). The seed is templated with the project’s directory name and the requested dependency groups. - Register groups. Sibling packages (or third-party apps) call
write_group_to_pyproject("name")to idempotently merge their dependency group into[dependency-groups]. User-pinned versions are preserved on conflict. - Audit. Before sync,
initialize_python()reports any pins that have drifted from the bundled reference spec. The user’s pins win and drift is surfaced as information, not corrected. - Lock + sync. uv refreshes
uv.lockagainst the project toml, thenuv sync --frozenmaterializes.venv/. With nogroupsargument the sync is--all-groups; with one or more groups it is--inexact --group <g>so sibling fyr-packages coexist additively.
The bundled spec is a reference, not the installed set. Bumping a
pin in pyro does not retroactively change projects whose
pyproject.toml already exists. Pins are kept and the next init
surfaces the drift.
Typical case: you don’t call pyro directly. The sibling package’s
initializer handles it.
reportifyr::initialize_report_project(here::here())
# or
presentifyr::initialize_app()You’ll be prompted once to confirm pyro can install uv, Python,
and the pinned dependencies. Subsequent calls are idempotent and fast
(uv reports no work when the venv is already in sync).
pyro::initialize_python() # all groups
pyro::initialize_python(groups = "reportifyr") # one group, additiveTo run Python from R against the resulting venv:
paths <- pyro::get_venv_uv_paths()
pyro::run_python_script(
uv_path = paths$uv,
venv_path = paths$venv,
args = c("run", "-m", "my_module"),
script_name = "my_module"
)Project-level (set up and inspect the environment):
| Function | Purpose |
|---|---|
initialize_python() |
Install uv, seed pyproject.toml, audit pins, run uv lock + uv sync. Prompts the user before touching their machine. |
write_group_to_pyproject(name, deps = NULL) |
Idempotently upsert a dependency group into the project’s pyproject.toml. Sibling packages call this from their wrappers; deps defaults to pyro’s bundled pins when name is a blessed group. |
get_proj_dir() |
Canonical resolver for the project root (getOption("venv_dir") if cached, else here::here()). The default for pyproject_dir / venv_dir arguments. |
Runtime (run Python and locate the toolchain):
| Function | Purpose |
|---|---|
get_venv_uv_paths() |
Return list(uv = ..., venv = ...). Errors clearly if uv or .venv/ are missing. |
run_python_script(uv_path, args, venv_path, script_name, ...) |
Run a Python script inside the venv via processx::run(). Supports optional PYTHONPATH, a stderr_callback, and a verbose_env passthrough. |
get_uv_path(quiet = FALSE) |
Pure lookup. Locate the uv binary on PATH or in known install locations, or NULL if absent. |
get_uv_version(uv_path) |
Parse uv --version for the installed version string. |
Sibling packages compose the two halves of the contract:
my_initialize <- function() {
pyro::write_group_to_pyproject(
name = "mypkg",
deps = c("pillow==11.1.0", "requests==2.31.0")
)
pyro::initialize_python(groups = "mypkg")
}write_group_to_pyproject() is purely a TOML edit;
initialize_python() does the install. Splitting them lets a wrapper
register its group declaratively without forcing a sync (e.g. when
chaining multiple wrappers).
For groups that pyro’s bundled spec already knows about
(reportifyr, presentifyr), omit deps forces the bundled pins to be
used. Third-party apps must supply deps explicitly.
venv_dirthroughout the codebase means the parent directory of.venv/(i.e. the project root when the venv lives at<project_root>/.venv/). Preserved for compatibility with existingreportifyrandpresentifyrprojects.pyproject_dirlets non-default project layouts (LLM agents, monorepos, closed-source projects with a secret toml elsewhere) pointpyroat an alternate location. Must already exist;pyrodoes not create project directories.getOption("venv_dir")caches the resolved project root after the first init; later calls in the same R session skip re-resolution.getOption("uv.version")pins the uv binary version. If unset,pyrouses whatever uv is already installed, or falls back to0.7.8on a clean machine.
