Skip to content

Bundle Submodule

SRToolkit.bundle

Bundle sharing utilities for the Symbolic Regression Toolkit.

Provides tools for packing, installing, and using bundles of user-defined Python code as .srtk archives. Bundles contain code only — configs (the settings a user chose) are intentionally separate so they can be shared, versioned, and tweaked independently of the implementation.

Workflow

Author side — pack the code:

from SRToolkit.bundle import pack

pack(
    files=["my_approach/approach.py", "my_approach/ops.py"],
    out_path="meznar_gp.srtk",
    name="meznar-gp",
    version="0.1.0",
    author="meznar",
    python_deps=["torch>=2.0"],
)

Share the .srtk file and a plain JSON config separately.

Recipient side — install and use:

import json
from SRToolkit.bundle import install

install("meznar_gp.srtk")   # one-time, interactive

raw = json.load(open("meznar_settings.srtk.json"))  # config shared separately
# The config is annotated with _bundle/_version by pack(..., configs=[...]),
# so it can be passed straight to any project `from_dict` consumer
# (SR_dataset, Sampler, the experiment grid, ...) — they call bind_config
# internally to repoint every *_class path at the installed bundle.
benchmark = SR_benchmark.from_dict(raw)

If the config was not annotated by pack (no _bundle key), bind it explicitly first, passing the bundle name:

from SRToolkit.bundle import bind_config

config = bind_config(raw, "meznar-gp")

Listing and removing:

from SRToolkit.bundle import list_installed, uninstall

for entry in list_installed():
    print(entry["name"], entry["version"])

uninstall("meznar-gp", version="0.1.0")

install

install(srtk_path: Path) -> None

Install a .srtk bundle onto this machine.

Steps:

  1. Unzip to a temporary directory and load the manifest.
  2. Verify per-file checksums declared in the manifest.
  3. Check the required SRToolkit version.
  4. Check Python dependencies — list missing ones and suggest a pip install command; the user decides whether to continue.
  5. Prompt the user to confirm that arbitrary user code will be executable.
  6. Copy src/ to the managed bundle directory.
  7. Register the bundle in the local index.

Parameters:

Name Type Description Default
srtk_path Path

Path to a .srtk bundle file produced by pack.

required

Raises:

Type Description
ValueError

On checksum mismatch.

RuntimeError

If the installed SRToolkit is too old.

Source code in SRToolkit/bundle/_install.py
def install(srtk_path: Path) -> None:
    """
    Install a ``.srtk`` bundle onto this machine.

    Steps:

    1. Unzip to a temporary directory and load the manifest.
    2. Verify per-file checksums declared in the manifest.
    3. Check the required ``SRToolkit`` version.
    4. Check Python dependencies — list missing ones and suggest a ``pip install``
       command; the user decides whether to continue.
    5. Prompt the user to confirm that arbitrary user code will be executable.
    6. Copy ``src/`` to the managed bundle directory.
    7. Register the bundle in the local index.

    Args:
        srtk_path: Path to a ``.srtk`` bundle file produced by
            [pack][SRToolkit.bundle.pack].

    Raises:
        ValueError: On checksum mismatch.
        RuntimeError: If the installed SRToolkit is too old.
    """
    srtk_path = Path(srtk_path)
    with tempfile.TemporaryDirectory() as tmp:
        tmp_dir = Path(tmp)
        manifest = _extract_and_verify(srtk_path, tmp_dir)

        missing_deps = _check_deps(manifest.python_deps)
        if missing_deps:
            print(f"Bundle '{manifest.name}' declares missing dependencies:")
            for dep in missing_deps:
                print(f"  {dep}")
            print(f"\nInstall them with:\n  pip install {' '.join(missing_deps)}\n")
            if not _confirm("Continue installation without them?"):
                print("Installation cancelled.")
                return

        print(
            f"\nBundle '{manifest.name}' v{manifest.version}"
            + (f" by {manifest.author!r}" if manifest.author else "")
            + " contains user-defined code that will run when loaded."
        )
        if not _confirm("Install?"):
            print("Installation cancelled.")
            return

        install_path = _store.bundle_path(manifest.name, manifest.version)
        if install_path.exists():
            shutil.rmtree(install_path)
        install_path.mkdir(parents=True)

        src_tmp = tmp_dir / "src"
        if src_tmp.exists():
            shutil.copytree(src_tmp, install_path, dirs_exist_ok=True)

        _store.register(manifest, install_path)
        _store.enable_bundle_imports()
        print(f"Installed '{manifest.name}' v{manifest.version}{install_path}")

list_installed

list_installed() -> list

Return a list of all installed bundle index entries.

Each entry is a dict with keys name, version, author, srtk_min_version, python_deps, path, and import_prefix.

Source code in SRToolkit/bundle/_install.py
def list_installed() -> list:
    """
    Return a list of all installed bundle index entries.

    Each entry is a dict with keys ``name``, ``version``, ``author``,
    ``srtk_min_version``, ``python_deps``, ``path``, and ``import_prefix``.
    """
    return _store.all_entries()

read_manifest

read_manifest(srtk_path: Path) -> BundleManifest

Read a .srtk bundle's manifest without installing it.

Reads only the manifest.json entry from the archive — the bundle's source files are not extracted, no checksums are verified, and the global bundle store is not touched. Use this to inspect a not-yet-installed bundle's name / version / import_prefix (e.g. to match referenced class paths against a bundle a user supplied to ExperimentGrid.export).

Parameters:

Name Type Description Default
srtk_path Path

Path to the .srtk archive.

required

Returns:

Type Description
BundleManifest

The bundle's BundleManifest.

Raises:

Type Description
FileNotFoundError

If srtk_path does not exist.

ValueError

If the archive is not a valid bundle (no manifest.json).

Source code in SRToolkit/bundle/_install.py
def read_manifest(srtk_path: Path) -> BundleManifest:
    """
    Read a ``.srtk`` bundle's manifest without installing it.

    Reads only the ``manifest.json`` entry from the archive — the bundle's source files
    are not extracted, no checksums are verified, and the global bundle store is not
    touched. Use this to inspect a not-yet-installed bundle's ``name`` / ``version`` /
    ``import_prefix`` (e.g. to match referenced class paths against a bundle a user
    supplied to [ExperimentGrid.export][SRToolkit.experiments.ExperimentGrid.export]).

    Args:
        srtk_path: Path to the ``.srtk`` archive.

    Returns:
        The bundle's [BundleManifest][SRToolkit.bundle._manifest.BundleManifest].

    Raises:
        FileNotFoundError: If ``srtk_path`` does not exist.
        ValueError: If the archive is not a valid bundle (no ``manifest.json``).
    """
    srtk_path = Path(srtk_path)
    if not srtk_path.is_file():
        raise FileNotFoundError(f"No bundle archive at {str(srtk_path)!r}.")
    try:
        with zipfile.ZipFile(srtk_path) as zf:
            raw = zf.read("manifest.json")
    except KeyError as exc:
        raise ValueError(f"{str(srtk_path)!r} is not a valid .srtk bundle (no manifest.json).") from exc
    return BundleManifest.from_dict(json.loads(raw.decode("utf-8")))

uninstall

uninstall(name: str, version: Optional[str] = None) -> None

Remove an installed bundle from this machine.

Parameters:

Name Type Description Default
name str

Bundle name.

required
version Optional[str]

Version to remove. If None, removes the latest installed version.

None
Source code in SRToolkit/bundle/_install.py
def uninstall(name: str, version: Optional[str] = None) -> None:
    """
    Remove an installed bundle from this machine.

    Args:
        name: Bundle name.
        version: Version to remove. If ``None``, removes the latest installed version.
    """
    entry = _store.lookup(name, version)
    install_path = Path(entry["path"])
    if install_path.exists():
        shutil.rmtree(install_path)
    _store.deregister(entry["name"], entry["version"])
    print(f"Uninstalled '{entry['name']}' v{entry['version']}")

pack

pack(files: Sequence[Union[str, Path]], out_path: Path, name: str, version: str, author: str = '', python_deps: List[str] | None = None, srtk_min_version: str = '', configs: Optional[Sequence[Union[str, Path]]] = None) -> None

Pack a list of Python source files into a .srtk bundle.

Each file is stored flat under src/ using its basename, so all filenames must be unique. Configs are intentionally excluded from the archive — share them separately as plain JSON. Class paths are rewritten at use-time by calling bind_config.

If configs is provided, each JSON config file is annotated with _bundle and _version metadata and written next to the original with a .srtk.json suffix (e.g. settings.jsonsettings.srtk.json). These annotated copies are the files to share alongside the .srtk bundle; the originals are left untouched.

The archive layout is::

manifest.json
src/
    <basename of each file>

Parameters:

Name Type Description Default
files Sequence[Union[str, Path]]

Paths to the .py files to include. Basenames must be unique.

required
out_path Path

Destination path for the .srtk file (created or overwritten).

required
name str

Bundle name (alphanumeric, hyphens allowed).

required
version str

Semantic version string, e.g. "0.2.1".

required
author str

Optional author identifier.

''
python_deps List[str] | None

List of PEP 508 dependency specifiers, e.g. ["torch>=2.0"].

None
srtk_min_version str

Minimum SRToolkit version required to run this bundle.

''
configs Optional[Sequence[Union[str, Path]]]

Optional list of JSON config file paths to annotate. Each is written to <stem>.srtk.json in the same directory.

None

Raises:

Type Description
FileNotFoundError

If any source or config file does not exist.

ValueError

If a source file is not .py, two source files share the same basename, or a config already contains _bundle or _version keys (indicates an already-annotated config).

Source code in SRToolkit/bundle/_pack.py
def pack(
    files: Sequence[Union[str, Path]],
    out_path: Path,
    name: str,
    version: str,
    author: str = "",
    python_deps: List[str] | None = None,
    srtk_min_version: str = "",
    configs: Optional[Sequence[Union[str, Path]]] = None,
) -> None:
    """
    Pack a list of Python source files into a ``.srtk`` bundle.

    Each file is stored flat under ``src/`` using its basename, so all filenames
    must be unique. Configs are intentionally excluded from the archive — share
    them separately as plain JSON. Class paths are rewritten at use-time by
    calling [bind_config][SRToolkit.bundle.bind_config].

    If ``configs`` is provided, each JSON config file is annotated with
    ``_bundle`` and ``_version`` metadata and written next to the original with
    a ``.srtk.json`` suffix (e.g. ``settings.json`` → ``settings.srtk.json``).
    These annotated copies are the files to share alongside the ``.srtk`` bundle;
    the originals are left untouched.

    The archive layout is::

        manifest.json
        src/
            <basename of each file>

    Args:
        files: Paths to the ``.py`` files to include. Basenames must be unique.
        out_path: Destination path for the ``.srtk`` file (created or overwritten).
        name: Bundle name (alphanumeric, hyphens allowed).
        version: Semantic version string, e.g. ``"0.2.1"``.
        author: Optional author identifier.
        python_deps: List of PEP 508 dependency specifiers, e.g. ``["torch>=2.0"]``.
        srtk_min_version: Minimum ``SRToolkit`` version required to run this bundle.
        configs: Optional list of JSON config file paths to annotate. Each is
            written to ``<stem>.srtk.json`` in the same directory.

    Raises:
        FileNotFoundError: If any source or config file does not exist.
        ValueError: If a source file is not ``.py``, two source files share the
            same basename, or a config already contains ``_bundle`` or
            ``_version`` keys (indicates an already-annotated config).
    """
    out_path = Path(out_path)
    python_deps = python_deps or []

    resolved: list[tuple[Path, str]] = []
    seen_basenames: dict[str, Path] = {}
    for raw in files:
        p = Path(raw).resolve()
        if not p.exists():
            raise FileNotFoundError(f"File not found: {p}")
        if p.suffix != ".py":
            raise ValueError(f"Only .py files are supported: {p}")
        if p.name in seen_basenames:
            raise ValueError(f"Duplicate basename {p.name!r}: {seen_basenames[p.name]} and {p}")
        seen_basenames[p.name] = p
        resolved.append((p, f"src/{p.name}"))

    archive_files: dict[str, str] = {}
    with zipfile.ZipFile(out_path, "w", compression=zipfile.ZIP_DEFLATED) as zf:
        for file_path, archive_name in resolved:
            zf.write(file_path, archive_name)
            archive_files[archive_name] = _sha256(file_path)

        manifest = BundleManifest(
            name=name,
            version=version,
            author=author,
            srtk_min_version=srtk_min_version,
            python_deps=python_deps,
            files=archive_files,
        )
        zf.writestr("manifest.json", json.dumps(manifest.to_dict(), indent=2))

    if configs:
        for cfg_raw in configs:
            cfg_path = Path(cfg_raw).resolve()
            if not cfg_path.exists():
                raise FileNotFoundError(f"Config file not found: {cfg_path}")
            cfg = json.loads(cfg_path.read_text(encoding="utf-8"))
            if "_bundle" in cfg or "_version" in cfg:
                raise ValueError(
                    f"{cfg_path.name} already contains '_bundle' or '_version' — "
                    "pass the original config, not an already-annotated one."
                )
            annotated = {"_bundle": name, "_version": version, **cfg}
            out = cfg_path.parent / (cfg_path.stem + ".srtk.json")
            out.write_text(json.dumps(annotated, indent=2), encoding="utf-8")

bind_config

bind_config(config: dict, bundle_name: Optional[str] = None, version: Optional[str] = None) -> dict

Rewrite *_class paths in config to point at an installed bundle.

Looks up bundle_name in the local bundle index and rewrites every *_class value so that it resolves to the installed copy's import prefix. Paths already starting with SRToolkit. are left unchanged.

If bundle_name is not provided, the _bundle and _version keys embedded in the config by pack are used as fallbacks. This allows calling bind_config(config) directly when the config was annotated at pack time.

This is the recipient-side complement to pack for configs: the author shares the .srtk file (code) and an annotated .srtk.json config; the recipient installs the bundle once and then calls bind_config to make the config usable on their machine.

Parameters:

Name Type Description Default
config dict

JSON-style configuration dictionary, optionally containing _bundle and _version metadata added by pack.

required
bundle_name Optional[str]

Name of an installed bundle. If None, config["_bundle"] is used.

None
version Optional[str]

Version to bind against. If None, config["_version"] is tried first, then the latest installed version.

None

Returns:

Type Description
dict

A new dictionary with rewritten *_class references ready for use

dict

with from_dict dispatchers. config is not modified and _bundle/

dict

_version metadata keys are stripped from the result.

Raises:

Type Description
ValueError

If bundle_name is not provided and config has no _bundle key.

KeyError

If the bundle (or requested version) is not installed.

LookupError

If a *_class value references a class that cannot be found in the installed bundle.

Source code in SRToolkit/bundle/_relocate.py
def bind_config(
    config: dict,
    bundle_name: Optional[str] = None,
    version: Optional[str] = None,
) -> dict:
    """
    Rewrite ``*_class`` paths in ``config`` to point at an installed bundle.

    Looks up ``bundle_name`` in the local bundle index and rewrites every
    ``*_class`` value so that it resolves to the installed copy's import prefix.
    Paths already starting with ``SRToolkit.`` are left unchanged.

    If ``bundle_name`` is not provided, the ``_bundle`` and ``_version`` keys
    embedded in the config by [pack][SRToolkit.bundle.pack] are used as
    fallbacks. This allows calling ``bind_config(config)`` directly when the
    config was annotated at pack time.

    This is the recipient-side complement to [pack][SRToolkit.bundle.pack] for configs:
    the author shares the ``.srtk`` file (code) and an annotated ``.srtk.json``
    config; the recipient installs the bundle once and then calls ``bind_config``
    to make the config usable on their machine.

    Args:
        config: JSON-style configuration dictionary, optionally containing
            ``_bundle`` and ``_version`` metadata added by ``pack``.
        bundle_name: Name of an installed bundle. If ``None``, ``config["_bundle"]``
            is used.
        version: Version to bind against. If ``None``, ``config["_version"]`` is
            tried first, then the latest installed version.

    Returns:
        A new dictionary with rewritten ``*_class`` references ready for use
        with ``from_dict`` dispatchers. ``config`` is not modified and ``_bundle``/
        ``_version`` metadata keys are stripped from the result.

    Raises:
        ValueError: If ``bundle_name`` is not provided and ``config`` has no
            ``_bundle`` key.
        KeyError: If the bundle (or requested version) is not installed.
        LookupError: If a ``*_class`` value references a class that cannot be
            found in the installed bundle.
    """
    resolved_name = bundle_name or config.get("_bundle")
    if not resolved_name:
        raise ValueError(
            "bundle_name must be provided or the config must contain a '_bundle' key "
            "(added automatically by pack(..., configs=[...]))."
        )
    resolved_version = version or config.get("_version")

    entry = _store.lookup(resolved_name, resolved_version)
    _store.enable_bundle_imports()

    stripped = {k: v for k, v in config.items() if k not in ("_bundle", "_version")}
    return _relocate_class_paths(stripped, entry["import_prefix"], entry["path"])

enable_bundle_imports

enable_bundle_imports() -> None

Make installed bundles importable in the current Python process.

Adds the bundles-root parent directory to sys.path (if not already present) so that import srtk_bundles.<safe_name>_<version_slug> works. Idempotent and safe to call repeatedly.

Called automatically at the end of install and by bind_config. Call it directly only when you want to import a bundle's classes by hand in a fresh Python session without going through from_dict / bind_config.

Source code in SRToolkit/bundle/_store.py
def enable_bundle_imports() -> None:
    """
    Make installed bundles importable in the current Python process.

    Adds the bundles-root parent directory to ``sys.path`` (if not already
    present) so that ``import srtk_bundles.<safe_name>_<version_slug>`` works.
    Idempotent and safe to call repeatedly.

    Called automatically at the end of [install][SRToolkit.bundle.install] and
    by [bind_config][SRToolkit.bundle.bind_config]. Call it directly only when
    you want to ``import`` a bundle's classes by hand in a fresh Python session
    without going through ``from_dict`` / ``bind_config``.
    """
    bundles_parent = str(bundles_root().parent)
    if bundles_parent not in sys.path:
        sys.path.insert(0, bundles_parent)

SRToolkit.bundle._manifest.BundleManifest dataclass

BundleManifest(name: str, version: str, author: str = '', srtk_min_version: str = '', python_deps: List[str] = list(), files: Dict[str, str] = dict())

verify

verify(extracted_dir: Path) -> None

Raise ValueError if any declared file is missing or has a wrong checksum.

Source code in SRToolkit/bundle/_manifest.py
def verify(self, extracted_dir: Path) -> None:
    """Raise ValueError if any declared file is missing or has a wrong checksum."""
    for rel, expected in self.files.items():
        p = extracted_dir / rel
        if not p.exists():
            raise ValueError(f"Bundle file missing: {rel}")
        actual = _sha256(p)
        if actual != expected:
            raise ValueError(f"Checksum mismatch for {rel}: expected {expected[:12]}…, got {actual[:12]}…")