Skip to content

Allow --rm to remove a virtualenv by its dependencies - #455

Open
gilgamezh wants to merge 3 commits into
masterfrom
worktree-rm-by-deps
Open

gilgamezh wants to merge 3 commits into
masterfrom
worktree-rm-by-deps

Conversation

@gilgamezh

Copy link
Copy Markdown
Contributor

What

--rm now accepts either a UUID or nothing. When given as a bare flag it removes the virtualenv matching the indicated dependencies, so this:

fades --rm $(fades --where -d django | awk -F "/" '{print $NF}')

becomes simply:

fades --rm -d django

How

  • --rm uses nargs='?' with a REMOVE_BY_DEPS sentinel as const, distinguishing "not passed" / "passed with UUID" / "passed bare".
  • The removal block was moved below the dependency/interpreter/options setup. With the sentinel it resolves the venv via get_venv(indicated_deps, interpreter, options=options) — the same matching used to run a venv — otherwise it uses the existing UUID lookup. Both share the destroy + warning logic.
  • Backward compatible: fades --rm <uuid> behaves exactly as before.

Tests

There were no tests for the --rm flow in go(). Added RemoveVenvTestCase covering removal by UUID (found / not found / invalid env_path) and by dependencies (found / not found).

  • Full suite: 296 passed
  • flake8: clean

Docs

Updated man/fades.1 and README.rst (usage synopsis + command examples).

🤖 Generated with Claude Code

@facundobatista facundobatista left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Awesome, thanks! There's a couple of very small details, commented inline...

Comment thread fades/main.py Outdated
Comment thread man/fades.1 Outdated

Copy link
Copy Markdown
Contributor Author

One edge case with --rm taking an optional value: when the dependencies come from a script, the script name gets consumed as the UUID.

With nargs='?', argparse grabs the next non-option token as the value, so (checked on 3.13):

command remove child_program
fades --rm -d foo sentinel ✅ None
fades --rm script.py 'script.py' ❌ None
fades --rm script.py arg 'script.py' ❌ 'arg'
fades --rm -- script.py sentinel ✅ 'script.py'
fades script.py --rm None ❌ 'script.py' (--rm goes to child_options)

So the natural "remove the venv for this script" (fades --rm script.py) ends up warning No virtualenv found with uuid: script.py. and exits 0, which is easy to miss. The only form that works today is --rm -- script.py.

Proposal: venv UUIDs are always str(uuid4()) (envbuilder.py), so a value that doesn't parse as a UUID can't be one. After parse_args(), reinterpret it as the child program:

if isinstance(args.remove, str):
    try:
        uuid.UUID(args.remove)
    except ValueError:
        # not a UUID: it's the child program, remove by its dependencies
        if args.child_program is not None:
            args.child_options.insert(0, args.child_program)
        args.child_program = args.remove
        args.remove = REMOVE_BY_DEPS

That makes fades --rm script.py do what it reads like, keeps fades --rm <uuid> unchanged, and needs no new flag. Plus a couple of tests for the reinterpretation.

If that's too magic, the conservative alternative is to error out on a non-UUID value, suggesting fades --rm -- script.py, instead of the current warning + exit 0.

Happy to push either one; I lean towards the first.


Generated by Claude Code

@gilgamezh

Copy link
Copy Markdown
Contributor Author

Pushed 76b3571 to fix the --rm script.py edge case from the comment above, without isinstance():

  • Bare --rm now uses const='' instead of an object() sentinel, so args.remove is always None or a str.
  • Right after parsing, if args.remove is not None and it isn't a valid UUID, it's the child program: it's moved to child_program (any previous child_program goes to the front of child_options), and the venv is removed by its dependencies.
  • In the removal block, a non-empty value means remove by UUID, and an empty one means remove by dependencies.
command result
fades --rm -d foo by deps
fades --rm <uuid> by UUID
fades --rm script.py by the script's deps (was: "No virtualenv found with uuid: script.py")
fades --rm script.py arg --opt by the script's deps, arg --opt kept as child options (was: "child program not found")
fades --rm -- script.py by the script's deps

Tests: the existing UUID tests now use a real UUID, and I added tests for --rm script.py, --rm script.py arg1 --opt and --rm -- script.py. The two script ones failed before the fix. Full suite: 299 passed, flake8 clean.


Generated by Claude Code

gilgamezh and others added 3 commits October 17, 2026 16:32
'--rm' now takes an optional UUID: when passed a UUID it removes that
virtualenv as before, but when given as a bare flag it removes the
virtualenv matching the indicated dependencies (e.g. 'fades --rm -d django'),
saving the round-trip through '--where' to look up the UUID.

Add tests for the '--rm' flow in go() (by UUID and by dependencies), and
update the man page and README accordingly.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
As '--rm' takes an optional UUID, argparse grabs the script as its value.
Venv UUIDs are always valid UUIDs, so a value that isn't one is taken as
the child program, removing the venv by its dependencies.

The bare '--rm' value is now an empty string instead of an object()
sentinel, so the value is always None or a str.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@gilgamezh
gilgamezh force-pushed the worktree-rm-by-deps branch from 76b3571 to b659f69 Compare October 17, 2026 14:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants