Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion plugins/kbagent/skills/kbagent/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -193,7 +193,7 @@ When working inside a git repository or project directory, run `kbagent init` (o
| Delete one or more storage tables | `kbagent storage delete-table --project PROJECT --table-id TABLE-ID` |
| Truncate (delete all rows from) one or more storage tables | `kbagent storage truncate-table --project PROJECT --table-id TABLE-ID` |
| Delete one or more columns from a storage table | `kbagent storage delete-column --project PROJECT --table-id TABLE-ID --column COLUMN` |
| Swap two storage tables in a development branch | `kbagent storage swap-tables --project PROJECT --table-id TABLE-ID --target-table-id TARGET-TABLE-ID` |
| Swap two storage tables (any branch, including the default/production branch) | `kbagent storage swap-tables --project PROJECT --table-id TABLE-ID --target-table-id TARGET-TABLE-ID` |
| Clone (pull) a production table into a development branch | `kbagent storage clone-table --project PROJECT --table-id TABLE-ID` |
| Delete one or more storage buckets | `kbagent storage delete-bucket --project PROJECT --bucket-id BUCKET-ID` |
| Set the description on a storage bucket | `kbagent storage describe-bucket --project PROJECT --bucket-id BUCKET-ID` |
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ Requires a **super-admin** Manage API token (same kind as `org setup`). Same def
- `storage truncate-table --project NAME --table-id ID [--table-id ...] [--dry-run] [--yes] [--branch ID]` (since v0.32.0) -- delete all rows while preserving table schema, primary key, descriptions, sharing edges, and downstream dependents. Batch via repeated `--table-id`. Endpoint is uniformly async-via-job on every branch (returns a queued `tableRowsDelete` job; client polls via `_wait_for_storage_job` before returning). Idempotent (truncating an empty table is a no-op). Use when re-seeding a table without losing the schema contract
- `storage delete-column --project NAME --table-id ID --column COL [--column ...] [--force] [--dry-run] [--yes] [--branch ID]` -- delete columns from a table (branch-aware)
- `storage delete-bucket --project NAME --bucket-id ID [--bucket-id ...] [--force] [--dry-run] [--yes] [--branch ID]` -- delete buckets (branch-aware)
- `storage swap-tables --project NAME --table-id ID --target-table-id ID --branch ID [--dry-run] [--yes]` (since v0.28.0) -- swap two storage tables in a dev branch (POST `/tables/{id}/swap`). Both tables exchange physical positions; aliases are NOT transferred (they keep pointing at the same physical position and therefore expose the OTHER table's data after the swap). Service refuses without a branch (active branch via `branch use` works too). Use to flip a typed rebuild ("data_change_log") into the original name ("data") without touching downstream config references
- `storage swap-tables --project NAME --table-id ID --target-table-id ID --branch ID [--dry-run] [--yes]` (since v0.28.0) -- swap two storage tables in any branch, including the default/production branch (POST `/tables/{id}/swap`). Both tables exchange physical positions; aliases are NOT transferred (they keep pointing at the same physical position and therefore expose the OTHER table's data after the swap). Service refuses without a branch (active branch via `branch use` works too). Use to flip a typed rebuild ("data_change_log") into the original name ("data") without touching downstream config references
- `storage clone-table --project NAME --table-id ID --branch ID [--dry-run]` (since v0.52.0) -- pull (clone) a production table into a dev branch (POST `/tables/{id}/pull`, operationName `devBranchTablePull`). On `storage-branches` projects a dev branch reads prod tables transparently until the first write, so an in-branch schema mutation (`swap-tables`, dropping a column) fails with a misleading "bucket not found" until the table is materialized branch-local; `clone-table` does that. One-way (default -> branch). Service refuses without a branch (active branch via `branch use` works too). Permission class `write`
- `storage describe-bucket --project NAME --bucket-id ID [--text STR | --file PATH | --stdin] [--branch ID]` -- set a bucket description (stored as `KBC.description` in bucket metadata, upsert). Provide exactly one of `--text`, `--file`, `--stdin`. Read back via `storage bucket-detail`
- `storage describe-table --project NAME --table-id ID [--text STR | --file PATH | --stdin] [--branch ID]` -- set a table description (stored as `KBC.description` in table metadata, upsert). Provide exactly one of `--text`, `--file`, `--stdin`. Read back via `storage table-detail`
Expand Down
2 changes: 1 addition & 1 deletion src/keboola_agent_cli/commands/context.py
Original file line number Diff line number Diff line change
Expand Up @@ -395,7 +395,7 @@
Delete one or more buckets. --force cascade-deletes tables. Linked/shared buckets protected. Branch-aware.

kbagent storage swap-tables --project NAME --table-id ID --target-table-id ID --branch ID [--dry-run] [--yes]
Swap two storage tables in a dev branch (POST /tables/{id}/swap). Both tables exchange physical positions;
Swap two storage tables in any branch, including the default/production branch (POST /tables/{id}/swap). Both tables exchange physical positions;
aliases are NOT transferred (they keep pointing at the same physical position and therefore expose the
OTHER table's data after the swap). Use to promote a typed rebuild back into the original name without
touching downstream config references. branch_id is mandatory (--branch or active branch via 'kbagent
Expand Down
2 changes: 1 addition & 1 deletion src/keboola_agent_cli/commands/storage.py
Original file line number Diff line number Diff line change
Expand Up @@ -1326,7 +1326,7 @@ def storage_swap_tables(
help="Skip confirmation prompt",
),
) -> None:
"""Swap two storage tables in a development branch.
"""Swap two storage tables (any branch, including the default/production branch).

Both tables exchange physical positions. Aliases are NOT transferred --
they keep pointing at the same physical position and therefore expose
Expand Down
8 changes: 4 additions & 4 deletions src/keboola_agent_cli/services/storage_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -1362,7 +1362,7 @@ def swap_tables(
alias: Project alias.
table_id: Full ID of the first table.
target_table_id: Full ID of the second table.
branch_id: Dev branch ID (must not be None).
branch_id: Branch ID (must not be None; any branch accepted, including the default/production branch).
dry_run: If True, only report what would be swapped.

Returns:
Expand All @@ -1375,10 +1375,10 @@ def swap_tables(
"""
if branch_id is None:
raise ConfigError(
"swap-tables requires a dev branch. Set one with "
"swap-tables requires a branch. Set one with "
"'kbagent branch use --project <P> --branch <ID>' or pass "
"--branch <ID> directly. The Storage API rejects this on "
"production."
"--branch <ID> directly. Any branch works, including the "
"default/production branch."
)

if table_id == target_table_id:
Expand Down
8 changes: 4 additions & 4 deletions tests/test_storage_swap.py
Original file line number Diff line number Diff line change
Expand Up @@ -219,12 +219,12 @@ def test_dry_run_skips_client_call(self, tmp_path: Path) -> None:
mock_client.swap_tables.assert_not_called()

def test_no_branch_raises_config_error(self, tmp_path: Path) -> None:
"""Mandatory branch enforcement: production swap is rejected before any HTTP."""
"""Mandatory branch enforcement: swap-tables without --branch or active branch raises ConfigError before any HTTP."""
store = _make_store(tmp_path)
mock_client = MagicMock()
service = _make_service(store, mock_client)

with pytest.raises(ConfigError, match="dev branch"):
with pytest.raises(ConfigError, match="requires a branch"):
service.swap_tables(
alias="test",
table_id="in.c-foo.a",
Expand Down Expand Up @@ -433,7 +433,7 @@ def test_swap_missing_branch_fails_clearly(self, tmp_path: Path) -> None:
):
MockStore.return_value = store
svc = MockSvc.return_value
svc.swap_tables.side_effect = ConfigError("swap-tables requires a dev branch.")
svc.swap_tables.side_effect = ConfigError("swap-tables requires a branch.")
result = runner.invoke(
app,
[
Expand All @@ -453,4 +453,4 @@ def test_swap_missing_branch_fails_clearly(self, tmp_path: Path) -> None:
assert result.exit_code == 5
payload = json.loads(result.output)
assert payload["status"] == "error"
assert "dev branch" in payload["error"]["message"]
assert "requires a branch" in payload["error"]["message"]
Loading