Skip to content

Commit b52675d

Browse files
authored
Merge pull request #51 from RafsanNeloy/remaining_phaseworks
Implement Remaining `plan.md` Tasks (16, 18, 19 & 21)
2 parents 09a9a1e + 6a91636 commit b52675d

19 files changed

Lines changed: 699 additions & 100 deletions

capture_tests/cbrain_cli_commands

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -79,10 +79,10 @@ cbrain --json project unswitch
7979
cbrain --jsonl project switch 10
8080
cbrain --jsonl project unswitch
8181

82-
cbrain project switch all # 'all' not yet implemented as of Aug 2025
82+
cbrain project switch all
8383
cbrain --json project switch all
8484
cbrain --jsonl project switch all
85-
cbrain project switch 3 # need to reset for rest of test to work
85+
cbrain project switch 3 # reset project filter for task list below
8686

8787
# Tags
8888
cbrain tag list
@@ -103,10 +103,7 @@ cbrain --json tag create --name NewTag2 --user-id 2 --group-id 3
103103
cbrain --jsonl tag create --name NewTag3 --user-id 2 --group-id 3
104104

105105
# Tasks
106-
# NOTE: we needed to reset the current project to 3 above,
107-
# until 'group switch all' is implemented; this means only
108-
# tasks in group 3 are shown below, but that's OK, that's
109-
# what's in the test DB anyway
106+
# Reset above to project 3 so task list matches seeded DB view.
110107
cbrain task list
111108
cbrain --json task list
112109
cbrain --jsonl task list
@@ -115,8 +112,11 @@ cbrain task show 2
115112
cbrain --json task show 2
116113
cbrain --jsonl task show 2
117114

118-
# Not yet implemented
119-
cbrain task operation # should provide error message
115+
# Task operations (hold on Completed task → skipped; deterministic, no BAC)
116+
cbrain task operation hold --task-id 2
117+
cbrain --json task operation hold --task-id 2
118+
cbrain --jsonl task operation hold --task-id 2
119+
cbrain task operation hold # missing --task-id / --batch-id
120120

121121
# ToolConfigs, as admin user
122122
./switch_session admin

capture_tests/expected_captures.txt

Lines changed: 72 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -558,40 +558,43 @@ Stderr:
558558
(No output)
559559

560560
############################
561-
Command: cbrain project switch all # 'all' not yet implemented as of Aug 2025
562-
Status: 1
563-
Stdout: 74 bytes
561+
Command: cbrain project switch all
562+
Status: 0
563+
Stdout: 32 bytes
564564
Stderr: 0 bytes
565565

566566
Stdout:
567-
Error: Project switch 'all' not yet implemented as of Aug 2025 (group_id)
567+
Current project is "all" ID=all
568568
Stderr:
569569
(No output)
570570

571571
############################
572572
Command: cbrain --json project switch all
573-
Status: 1
574-
Stdout: 74 bytes
573+
Status: 0
574+
Stdout: 35 bytes
575575
Stderr: 0 bytes
576576

577577
Stdout:
578-
Error: Project switch 'all' not yet implemented as of Aug 2025 (group_id)
578+
{
579+
"id": "all",
580+
"name": "all"
581+
}
579582
Stderr:
580583
(No output)
581584

582585
############################
583586
Command: cbrain --jsonl project switch all
584-
Status: 1
585-
Stdout: 74 bytes
587+
Status: 0
588+
Stdout: 26 bytes
586589
Stderr: 0 bytes
587590

588591
Stdout:
589-
Error: Project switch 'all' not yet implemented as of Aug 2025 (group_id)
592+
{"id":"all","name":"all"}
590593
Stderr:
591594
(No output)
592595

593596
############################
594-
Command: cbrain project switch 3 # need to reset for rest of test to work
597+
Command: cbrain project switch 3 # reset project filter for task list below
595598
Status: 0
596599
Stdout: 31 bytes
597600
Stderr: 0 bytes
@@ -998,18 +1001,67 @@ Stderr:
9981001
(No output)
9991002

10001003
############################
1001-
Command: cbrain task operation # should provide error message
1004+
Command: cbrain task operation hold --task-id 2
10021005
Status: 0
1003-
Stdout: 41 bytes
1006+
Stdout: 182 bytes
1007+
Stderr: 0 bytes
1008+
1009+
Stdout:
1010+
TASK OPERATION RESULT
1011+
------------------------------
1012+
Operation: hold
1013+
Succeeded: 0
1014+
Skipped: 1
1015+
Failed: 0
1016+
1017+
SKIPPED
1018+
------------------------------
1019+
Tasks have incompatible states: 2
1020+
Stderr:
1021+
(No output)
1022+
1023+
############################
1024+
Command: cbrain --json task operation hold --task-id 2
1025+
Status: 0
1026+
Stdout: 142 bytes
10041027
Stderr: 0 bytes
10051028

10061029
Stdout:
10071030
{
1008-
"message": "No operation selected"
1031+
"skipped_list": {
1032+
"Tasks have incompatible states": [
1033+
2
1034+
]
1035+
},
1036+
"success_list": [],
1037+
"failed_list": {},
1038+
"bac_ids": []
10091039
}
10101040
Stderr:
10111041
(No output)
10121042

1043+
############################
1044+
Command: cbrain --jsonl task operation hold --task-id 2
1045+
Status: 0
1046+
Stdout: 104 bytes
1047+
Stderr: 0 bytes
1048+
1049+
Stdout:
1050+
{"skipped_list":{"Tasks have incompatible states":[2]},"success_list":[],"failed_list":{},"bac_ids":[]}
1051+
Stderr:
1052+
(No output)
1053+
1054+
############################
1055+
Command: cbrain task operation hold # missing --task-id / --batch-id
1056+
Status: 1
1057+
Stdout: 68 bytes
1058+
Stderr: 0 bytes
1059+
1060+
Stdout:
1061+
Error: At least one --task-id or --batch-id is required (--task-id)
1062+
Stderr:
1063+
(No output)
1064+
10131065
############################
10141066
Command: ./switch_session admin
10151067
Status: 0
@@ -1187,36 +1239,36 @@ Stderr:
11871239
############################
11881240
Command: cbrain tool-config show 19 # visible to user admin
11891241
Status: 1
1190-
Stdout: 107 bytes
1242+
Stdout: 117 bytes
11911243
Stderr: 0 bytes
11921244

11931245
Stdout:
11941246
Authentication error (401): Unauthorized
1195-
Error: Access denied. Please log in using authorized credentials.
1247+
Error: Session expired or invalid. Run 'cbrain logout' then 'cbrain login'.
11961248
Stderr:
11971249
(No output)
11981250

11991251
############################
12001252
Command: cbrain --json tool-config show 19
12011253
Status: 1
1202-
Stdout: 107 bytes
1254+
Stdout: 117 bytes
12031255
Stderr: 0 bytes
12041256

12051257
Stdout:
12061258
Authentication error (401): Unauthorized
1207-
Error: Access denied. Please log in using authorized credentials.
1259+
Error: Session expired or invalid. Run 'cbrain logout' then 'cbrain login'.
12081260
Stderr:
12091261
(No output)
12101262

12111263
############################
12121264
Command: cbrain --jsonl tool-config show 19
12131265
Status: 1
1214-
Stdout: 107 bytes
1266+
Stdout: 117 bytes
12151267
Stderr: 0 bytes
12161268

12171269
Stdout:
12181270
Authentication error (401): Unauthorized
1219-
Error: Access denied. Please log in using authorized credentials.
1271+
Error: Session expired or invalid. Run 'cbrain logout' then 'cbrain login'.
12201272
Stderr:
12211273
(No output)
12221274

cbrain_cli/cli_utils.py

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,20 @@
1313
from cbrain_cli import config as cbrain_config
1414
from cbrain_cli.config import DEFAULT_HEADERS, DEFAULT_TIMEOUT, auth_headers
1515

16+
_debug = False
17+
18+
19+
def set_debug(flag: bool) -> None:
20+
"""Enable or disable debug output."""
21+
global _debug
22+
_debug = bool(flag)
23+
24+
25+
def debug_log(message: str) -> None:
26+
"""Print a debug line to stderr when debug mode is active."""
27+
if _debug:
28+
print(f"[DEBUG] {message}", file=sys.stderr)
29+
1630

1731
class CbrainClient:
1832
"""
@@ -46,11 +60,23 @@ def _request(self, method, path, *, headers=None, body=None, params=None):
4660
if params:
4761
target = f"{target}?{urllib.parse.urlencode(params)}"
4862
hdrs = headers or auth_headers(self.token)
63+
# strip host from full URL for debug display; preserve all query params
64+
parsed = urllib.parse.urlsplit(target)
65+
display_path = parsed.path
66+
if display_path.startswith(self.base_url):
67+
display_path = display_path[len(self.base_url) :]
68+
query = urllib.parse.urlencode(params) if params else parsed.query
69+
if query:
70+
display_path = f"{display_path}?{query}"
71+
debug_log(f"{method} {display_path}")
4972
req = urllib.request.Request(target, data=body, headers=hdrs, method=method)
5073
try:
5174
with urllib.request.urlopen(req, timeout=self.timeout) as r:
52-
return r.read(), r.status
75+
status = r.status
76+
debug_log(f"→ HTTP {status}")
77+
return r.read(), status
5378
except urllib.error.HTTPError as e:
79+
debug_log(f"→ HTTP {e.code} ({e.reason})")
5480
raise CliApiError(e.reason or f"HTTP {e.code}", status=e.code) from e
5581

5682
def get(self, path, params=None):
@@ -95,7 +121,7 @@ def post_multipart(self, path, body, content_type):
95121

96122
PAGINATABLE_ACTIONS = {
97123
("file", "list"),
98-
("dataprovider", "list"),
124+
("data-provider", "list"),
99125
("tool", "list"),
100126
("tool-config", "list"),
101127
("tag", "list"),
@@ -204,7 +230,7 @@ def handle_connection_error(error):
204230

205231
if error.code == 401:
206232
print(f"{status_description}: {error.reason}")
207-
print("Error: Access denied. Please log in using authorized credentials.")
233+
print("Error: Session expired or invalid. Run 'cbrain logout' then 'cbrain login'.")
208234
elif error.code in (400, 404, 422, 500):
209235
# Try to extract specific error message from response
210236
try:

cbrain_cli/data/files.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,9 @@ def _change_provider(args, operation):
9292
if not file_ids:
9393
raise CliValidationError("File ID(s) are required", field="--file-id")
9494
if not dest_provider_id:
95-
raise CliValidationError("Destination data provider ID is required", field="--dp-id")
95+
raise CliValidationError(
96+
"Destination data provider ID is required", field="--data-provider-id"
97+
)
9698
payload = {
9799
"file_ids": file_ids,
98100
"data_provider_id_for_mv_cp": dest_provider_id,

cbrain_cli/data/projects.py

Lines changed: 21 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -25,23 +25,25 @@ def switch_project(args):
2525
if not group_id:
2626
raise CliValidationError("Group ID is required", field="group_id")
2727

28-
if group_id == "all":
29-
raise CliValidationError(
30-
"Project switch 'all' not yet implemented as of Aug 2025", field="group_id"
31-
)
32-
33-
try:
34-
group_id = int(group_id)
35-
except ValueError:
36-
raise CliValidationError(
37-
f"Invalid group ID '{group_id}'. Must be a number or 'all'", field="group_id"
38-
) from None
28+
# Server accepts numeric id or "all" (no single project filter).
29+
if group_id != "all":
30+
try:
31+
group_id = int(group_id)
32+
except ValueError:
33+
raise CliValidationError(
34+
f"Invalid group ID '{group_id}'. Must be a number or 'all'", field="group_id"
35+
) from None
3936

4037
client = CbrainClient.from_credentials()
4138
_, switch_status = client.send("POST", f"/groups/switch?id={group_id}")
4239
if switch_status not in (200, 201, 204):
4340
raise CliApiError(f"Failed to switch project (HTTP {switch_status})")
44-
group_data = client.get(f"/groups/{group_id}")
41+
42+
# "all" is session state only — no /groups/all resource.
43+
if group_id == "all":
44+
group_data = {"id": "all", "name": "all"}
45+
else:
46+
group_data = client.get(f"/groups/{group_id}")
4547

4648
credentials = load_credentials()
4749
if credentials is not None:
@@ -124,6 +126,13 @@ def show_project(args):
124126
if not current_group_id:
125127
return None
126128

129+
# Session "all" has no Group row; mirror switch_project synthetic result.
130+
if current_group_id == "all":
131+
return {
132+
"id": "all",
133+
"name": credentials.get("current_group_name") or "all",
134+
}
135+
127136
try:
128137
return CbrainClient.from_credentials().get(f"/groups/{current_group_id}")
129138
except CliApiError as e:

0 commit comments

Comments
 (0)