Skip to content

A2-ai/pyro

Repository files navigation

pyro

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.

Installation

pak::pkg_install("a2-ai/pyro")

How it works

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.

  1. Seed (first init only). If the project has no pyproject.toml, initialize_python() seeds one from pyro’s bundled reference spec (inst/extdata/pyproject.toml). The seed is templated with the project’s directory name and the requested dependency groups.
  2. 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.
  3. 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.
  4. Lock + sync. uv refreshes uv.lock against the project toml, then uv sync --frozen materializes .venv/. With no groups argument 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.

Quick start

As an end user of a sibling fyr-package

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).

Directly, in any R project

pyro::initialize_python()                       # all groups
pyro::initialize_python(groups = "reportifyr")  # one group, additive

To 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"
)

Public API

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.

Authoring a fyr-package

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.

Conventions and overrides

  • venv_dir throughout 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 existing reportifyr and presentifyr projects.
  • pyproject_dir lets non-default project layouts (LLM agents, monorepos, closed-source projects with a secret toml elsewhere) point pyro at an alternate location. Must already exist; pyro does 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, pyro uses whatever uv is already installed, or falls back to 0.7.8 on a clean machine.

About

No description, website, or topics provided.

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors

Languages