Skip to content

muxtools.utils.binaries.operations

Catalog-backed managed executable installations.

plan_removal

plan_removal(config: ProjectConfig | None, scope: str, selectors: list[str]) -> RemovalPlan

Choose managed installations to remove without changing project declarations.

Source code in muxtools/utils/binaries/operations.py
def plan_removal(config: ProjectConfig | None, scope: str, selectors: list[str]) -> RemovalPlan:
    """Choose managed installations to remove without changing project declarations."""
    if scope not in ("local", "global"):
        raise ValueError("Choose --local or --global")
    if scope == "global" and "*" in selectors:
        raise ValueError("Global removal requires explicit package or version selectors")
    root = scope_path(config, scope)
    items = installed(root)
    try:
        catalog = load_catalog(offline=True)
    except (ValueError, OSError):
        catalog = None
    selected: set[tuple[str, str]] = set()
    declarations: set[str] = set()
    if "*" in selectors:
        if len(selectors) != 1:
            raise ValueError("'*' cannot be combined with other selectors")
        protected: set[tuple[str, str]] = set()
        if config and config.mode == "local":
            for raw in config.packages:
                spec = parse_spec(raw)
                name = resolve_installed_name(spec.name, items, catalog)
                package = catalog["packages"].get(name, {}) if catalog else {}
                try:
                    matches = usable_installed(Spec(name, spec.operator, spec.version), package, root, items)
                except ValueError:
                    matches = []
                if matches:
                    chosen = max(matches, key=lambda item: item["version_code"])
                    protected.add((name, chosen["version"]))
        selected = {(item["name"], item["version"]) for item in items} - protected
    else:
        for raw in selectors:
            spec = parse_spec(raw)
            name = resolve_installed_name(spec.name, items, catalog)
            package = catalog["packages"].get(name, {}) if catalog else {}
            if spec.operator is None:
                selected.update((item["name"], item["version"]) for item in items if item["name"] == name)
                if scope == "local" and config and config.mode == "local":
                    declarations.update(raw for raw in config.packages if resolve_installed_name(parse_spec(raw).name, items, catalog) == name)
            else:
                matches = matching_installed(Spec(name, spec.operator, spec.version), package, root, items)
                selected.update((item["name"], item["version"]) for item in matches)
    targets = tuple(item for item in items if (item["name"], item["version"]) in selected)
    for item in targets:
        managed_install_directory(root, item)
    return RemovalPlan(root, targets, tuple(raw for raw in config.packages if raw in declarations) if config else (), config)

apply_removal

apply_removal(plan: RemovalPlan) -> RemovalResult

Remove planned versions, then edit local declarations for fully removed packages.

Source code in muxtools/utils/binaries/operations.py
def apply_removal(plan: RemovalPlan) -> RemovalResult:
    """Remove planned versions, then edit local declarations for fully removed packages."""
    removed: list[dict[str, Any]] = []
    failed: list[RemovalFailure] = []
    for item in plan.items:
        try:
            shutil.rmtree(managed_install_directory(plan.root, item))
        except (OSError, ValueError) as error:
            failed.append(RemovalFailure(item, error))
        else:
            removed.append(item)
    changed: list[str] = []
    if plan.config and plan.declarations:
        failed_names = {failure.item["name"] for failure in failed}
        changed = [raw for raw in plan.declarations if resolve_installed_name(parse_spec(raw).name, plan.items) not in failed_names]
        if changed:
            update_config(plan.config.path, packages=[raw for raw in plan.config.packages if raw not in changed])
    installed(plan.root)
    return RemovalResult(tuple(removed), tuple(failed), tuple(changed))

remove

remove(config: ProjectConfig, names: list[str]) -> ProjectConfig

Remove declarations from a project config (legacy Python API).

Source code in muxtools/utils/binaries/operations.py
def remove(config: ProjectConfig, names: list[str]) -> ProjectConfig:
    """Remove declarations from a project config (legacy Python API)."""
    wanted = set(names)
    return update_config(config.path, packages=[raw for raw in config.packages if parse_spec(raw).name not in wanted])