Skip to content

feat: delete (soft) and restore training runs via SDK and CLI - #512

Open
leeclemnet wants to merge 7 commits into
mainfrom
feat/trainings-delete-restore
Open

feat: delete (soft) and restore training runs via SDK and CLI#512
leeclemnet wants to merge 7 commits into
mainfrom
feat/trainings-delete-restore

Conversation

@leeclemnet

@leeclemnet leeclemnet commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

What does this PR do?

Adds SDK and CLI support for the platform's new training deletion feature (roboflow/roboflow#13869), which is soft-delete-first: deleting a training moves it (and every model it produced) to the workspace Trash for 30 days, hidden from listings but restorable, before permanent cleanup.

  • rfapi.delete_version_training(...) — thin wrapper for DELETE /{ws}/{project}/{version}/v2/trainings/{training_id} (the platform's entity trash pattern; training_id required). Restore goes through the existing rfapi.restore_trash_item(..., "training", id) — the same shared POST /{ws}/trash/restore route project/version/workflow restores use; there is no bespoke trainings restore route. rfapi.resolve_version_training_id(...) resolves an omitted id to the version's sole run client-side (several runs raise MULTIPLE_TRAININGS with the ids listed).
  • Version.delete_training(training_id=None) / Version.restore_training(training_id) — mirror the existing Version.delete()/Version.restore() trash semantics; Training.delete()/Training.restore() do the same on the run object returned by the trainings APIs.
  • CLIroboflow train delete <project>/<version> [--training-id ...] and roboflow train restore <project>/<version> --training-id ..., with actionable hints for the refusal cases (in-flight run → stop/cancel first; run backs the version's registered model; multi-run version needs --training-id; item not in trash → roboflow trash list). roboflow train list <project>/<version> enumerates a version's runs with their ids (reading the API's id field) so MMPV training ids are discoverable. Blank/whitespace --training-id values fail as structured usage errors (exit code 2) instead of raw tracebacks, and restore_trash_item validates its id for every trash type.

Deletion is soft-only on the public API by design — permanent deletion stays in the web UI's Trash view, consistent with the existing note in rfapi that permanent trash actions are never exposed to API keys.

Related Issue(s): Depends on roboflow/roboflow#13869 (platform endpoints) — merge/deploy that first; until then these calls return 404.

Type of Change

  • New feature (non-breaking change that adds functionality)

Testing

  • I have tested this change locally
  • I have added/updated tests for this change

Test details:

> uv run roboflow train --help

 Usage: roboflow train [OPTIONS] [COMMAND] [ARGS]...

 Train a model

╭─ Options ────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
│ --checkpoint            TEXT     Checkpoint to resume training from                                                  │
│ --epochs                INTEGER  Number of training epochs                                                           │
│ --project       -p      TEXT     Project ID to train                                                                 │
│ --speed                 TEXT     Training speed preset                                                               │
│ --type          -t      TEXT     Model type (e.g. rfdetr-nano, yolov8n)                                              │
│ --train-recipe          TEXT     Full trainRecipe as inline JSON or @path/to/file.json (see 'roboflow train          │
│                                  recipe'); --epochs is folded into its hyperparameters unless the recipe already     │
│                                  sets epochs                                                                         │
│ --version       -v      INTEGER  Version number to train                                                             │
│ --help          -h               Show this message and exit.                                                         │
╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
╭─ Commands ───────────────────────────────────────────────────────────────────────────────────────────────────────────╮
│ cancel   Cancel an in-flight training run.                                                                           │
│ delete   Move a terminal training run to the workspace Trash (soft delete).                                          │
│ list     List a version's training runs with their ids.                                                              │
│ recipe   Show the training recipe schema and template for a model type.                                              │
│ restore  Restore a trashed training run (and its models) back into listings.                                         │
│ results  Run-level training results bundle.                                                                          │
│ start    Start training for a dataset version.                                                                       │
│ stop     Request a graceful early-stop on an in-flight training run.                                                 │
╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
 > uv run roboflow train delete --help

 Usage: roboflow train delete [OPTIONS] TARGET

 Move a terminal training run to the workspace Trash (soft delete).

 The run and every model it produced disappear from listings but stay
 restorable for 30 days ('roboflow train restore' or the web Trash view),
 after which they are permanently deleted. In-flight runs are refused —
 stop or cancel first — as is the run backing the version's registered
 model. Permanent deletion is only available in the web UI's Trash view.

╭─ Arguments ──────────────────────────────────────────────────────────────────────────────────────────────────────────╮
│ *    target      TEXT  Training to delete as 'project/version' [required]                                            │
╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
╭─ Options ────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
│ --training-id          TEXT  Training id of the run to delete (versions can own several). Omit to target the         │
│                              version's sole run.                                                                     │
│ --help         -h            Show this message and exit.                                                             │
╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
> uv run roboflow train delete chess-pieces-fmhpz/112 --training-id b704c8e6ee031f3ef563

Training moved to Trash for chess-pieces-fmhpz version 112. Restorable for 30 days via 'roboflow train restore'.
image image
> uv run roboflow train restore chess-pieces-fmhpz/112 --training-id b704c8e6ee031f3ef563

Training restored for chess-pieces-fmhpz version 112.
image image

Checklist

  • My code follows the style guidelines of this project
  • I have performed a self-review of my own code
  • I have commented my code where necessary, particularly in hard-to-understand areas
  • My changes generate no new warnings or errors
  • I have updated the documentation accordingly (if applicable)

Additional Context

Mirrors the existing cancel_version_training/stop_version_training adapter pattern and the train cancel/train stop CLI command pattern. The companion MCP PR (roboflow/roboflow-mcp#124) was closed by design: MCP has no trash-lifecycle tools for any entity yet, so trainings won't lead there.

🤖 Generated with Claude Code

Wires the platform's external v2 trainings delete/restore routes:

- rfapi: delete_version_training (POST .../v2/trainings/delete, optional
  trainingId with sole-run fallback) and restore_version_training
  (trainingId required — trashed runs are invisible to the sole-run
  resolver).
- SDK: Version.delete_training(training_id=None) and
  Version.restore_training(training_id), mirroring Version.delete()/
  restore() trash semantics: the run and its models hide from listings
  but stay restorable for 30 days before permanent cleanup.
- CLI: `roboflow train delete <project>/<version> [--training-id]` and
  `roboflow train restore <project>/<version> --training-id`, with
  actionable hints for in-progress runs, alias-backed models, and
  multi-run versions.

Deletion is soft-only on the public API by design; permanent deletion
stays in the web UI's Trash view.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
leeclemnet and others added 6 commits July 24, 2026 16:12
CI installs unpinned ruff; 0.16.0 (2026-07-23) starts enforcing CPY001
(missing copyright notice) under select = ["ALL"], failing every file.
Add the CPY family to the ignore list alongside the other excluded
families. Verified with ruff 0.16.0: format, check, and mypy all pass.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…y path

- delete_version_training omits trainingId only when None and raises
  ValueError on blank/whitespace ids, so an empty selector can never
  fall back to soft-deleting the version's sole run;
  restore_version_training rejects blank ids likewise.
- New `roboflow train list <project>/<version>` enumerates a version's
  runs (TRAINING_ID / STATUS / MODEL_TYPE / MODELS), and the
  MULTIPLE_TRAININGS hint on delete now points at it instead of
  `train results`, which cannot enumerate runs.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The platform refactored training deletion to match project/version/workflow:
HTTP DELETE on the resource and restore via the shared workspace trash route.

- delete_version_training now issues
  DELETE /{ws}/{proj}/{ver}/v2/trainings/{training_id} (id required) and
  returns the shared { deleted, type, ..., trash: true } shape.
- restore_version_training is gone; Training.restore, Version.restore_training,
  and 'roboflow train restore' go through restore_trash_item(..., "training", id) —
  the same route project/version/workflow restores use.
- Sole-run resolution moves client-side (resolve_version_training_id):
  Version.delete_training() and 'roboflow train delete' without --training-id
  list the version's runs and only proceed when there is exactly one, keeping
  the MULTIPLE_TRAININGS error + 'roboflow train list' hint.
- CLI restore hints now match the trash route's "not found in trash" message.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The v2 trainings list serializes each run's id as `id` (the pre-existing
serializeExternalTraining shape, shared with MCP), not `trainingId` — the CLI
table rendered an empty TRAINING_ID column and the sole-run resolver's
MULTIPLE_TRAININGS message listed "None". Read `id`, correct the docstring,
and point the test fixtures at the real payload shape (the old fixtures used
the wrong key, which is exactly what masked this).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Review follow-up: a blank --training-id raised a bare ValueError past the
CLI's error path, and restore forwarded whitespace ids to the API.

- restore_trash_item validates item_id (shared by every trash restore).
- train delete/restore map ValueError through output_error with exit code 2
  and a --training-id hint.
- Tests: structured error + exit code for blank delete/restore, blank
  Training.restore(), and blank restore_trash_item ids.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
External SDK users have no context for "DNA" or "MMPV" — say what the
commands and methods do in product terms instead.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@leeclemnet
leeclemnet marked this pull request as ready for review July 27, 2026 17:12
@leeclemnet
leeclemnet requested a review from mkaic July 27, 2026 17:14

@mkaic mkaic left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

LGTM

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.

2 participants