accli 2.1.0__tar.gz → 2.1.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. {accli-2.1.0 → accli-2.1.1}/PKG-INFO +9 -9
  2. {accli-2.1.0 → accli-2.1.1}/README.md +7 -7
  3. {accli-2.1.0 → accli-2.1.1}/accli/_version.py +1 -1
  4. {accli-2.1.0 → accli-2.1.1}/accli/cli.py +20 -46
  5. {accli-2.1.0 → accli-2.1.1}/accli/gui.py +2 -1
  6. {accli-2.1.0 → accli-2.1.1}/accli/job_views.py +3 -3
  7. {accli-2.1.0 → accli-2.1.1}/accli.egg-info/PKG-INFO +9 -9
  8. {accli-2.1.0 → accli-2.1.1}/pyproject.toml +1 -1
  9. {accli-2.1.0 → accli-2.1.1}/tests/test_jobs_commands.py +33 -13
  10. {accli-2.1.0 → accli-2.1.1}/tests/test_logs_command.py +3 -1
  11. {accli-2.1.0 → accli-2.1.1}/LICENSE +0 -0
  12. {accli-2.1.0 → accli-2.1.1}/accli/AcceleratorJobProjectService.py +0 -0
  13. {accli-2.1.0 → accli-2.1.1}/accli/AcceleratorTaskDispatcher.py +0 -0
  14. {accli-2.1.0 → accli-2.1.1}/accli/AcceleratorTerminalCliProjectService.py +0 -0
  15. {accli-2.1.0 → accli-2.1.1}/accli/CsvRegionalTimeseriesValidator.py +0 -0
  16. {accli-2.1.0 → accli-2.1.1}/accli/__init__.py +0 -0
  17. {accli-2.1.0 → accli-2.1.1}/accli/__main__.py +0 -0
  18. {accli-2.1.0 → accli-2.1.1}/accli/cleanup-nfs.ps1 +0 -0
  19. {accli-2.1.0 → accli-2.1.1}/accli/common.py +0 -0
  20. {accli-2.1.0 → accli-2.1.1}/accli/log_timeline.py +0 -0
  21. {accli-2.1.0 → accli-2.1.1}/accli/mount_downloader.py +0 -0
  22. {accli-2.1.0 → accli-2.1.1}/accli/nfs_watcher.py +0 -0
  23. {accli-2.1.0 → accli-2.1.1}/accli/refresh_explorer.ps1 +0 -0
  24. {accli-2.1.0 → accli-2.1.1}/accli/setup-nfs.ps1 +0 -0
  25. {accli-2.1.0 → accli-2.1.1}/accli/token.py +0 -0
  26. {accli-2.1.0 → accli-2.1.1}/accli/version_checker.py +0 -0
  27. {accli-2.1.0 → accli-2.1.1}/accli.egg-info/SOURCES.txt +0 -0
  28. {accli-2.1.0 → accli-2.1.1}/accli.egg-info/dependency_links.txt +0 -0
  29. {accli-2.1.0 → accli-2.1.1}/accli.egg-info/entry_points.txt +0 -0
  30. {accli-2.1.0 → accli-2.1.1}/accli.egg-info/requires.txt +0 -0
  31. {accli-2.1.0 → accli-2.1.1}/accli.egg-info/top_level.txt +0 -0
  32. {accli-2.1.0 → accli-2.1.1}/setup.cfg +0 -0
  33. {accli-2.1.0 → accli-2.1.1}/tests/test_log_timeline.py +0 -0
  34. {accli-2.1.0 → accli-2.1.1}/tests/test_mount_lifecycle.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: accli
3
- Version: 2.1.0
3
+ Version: 2.1.1
4
4
  Summary: IIASA Accelerator Client
5
5
  Author-email: Wrufesh S <wrufesh@gmail.com>
6
6
  License: The MIT License (MIT)
@@ -29,7 +29,7 @@ Project-URL: Bug Tracker, https://github.com/iiasa/accli/issues
29
29
  Classifier: Programming Language :: Python :: 3
30
30
  Classifier: License :: OSI Approved :: MIT License
31
31
  Classifier: Operating System :: OS Independent
32
- Requires-Python: >=3.7
32
+ Requires-Python: >=3.8
33
33
  Description-Content-Type: text/markdown
34
34
  License-File: LICENSE
35
35
  Requires-Dist: urllib3>=1.26.5
@@ -137,19 +137,19 @@ Usage: accli [OPTIONS] COMMAND [ARGS]...
137
137
  List root jobs, newest first, with each job's type (`standalone`, `children`, `callback` or `children+callback`), its own status and the status of its whole graph:
138
138
 
139
139
  ```bash
140
- accli jobs # latest 10 in the last project used
141
- accli jobs -p my-project -n 50
142
- accli jobs -s failed # graphs with a failed job
143
- accli jobs -s processing,error --all
140
+ accli jobs my-project # latest 10
141
+ accli jobs my-project -n 50
142
+ accli jobs my-project -s failed # graphs with a failed job
143
+ accli jobs my-project -s processing,error
144
144
  ```
145
145
 
146
146
  `--status` filters on the graph status: `processing` (or `active`), `error` (or `failed`) and `done`. A standalone job counts by its own status.
147
147
 
148
- Show a job's status and details. For a job holding children or callbacks, Graph status covers the jobs below it (the same as GRAPH in `accli jobs`) and each one is listed; any job also links to the pipeline it is part of. Read a job's output with `accli logs <job>`:
148
+ Show a job's status and details. For a job holding children or callbacks, Graph status covers the jobs below it (the same as GRAPH in `accli jobs`) and each one is listed; any job also links to the pipeline it is part of. Read a job's output with `accli logs <project> <job>`:
149
149
 
150
150
  ```bash
151
- accli job 1234
152
- accli job 1234 --json
151
+ accli job my-project 1234
152
+ accli job my-project 1234 --json
153
153
  ```
154
154
 
155
155
  ---
@@ -93,19 +93,19 @@ Usage: accli [OPTIONS] COMMAND [ARGS]...
93
93
  List root jobs, newest first, with each job's type (`standalone`, `children`, `callback` or `children+callback`), its own status and the status of its whole graph:
94
94
 
95
95
  ```bash
96
- accli jobs # latest 10 in the last project used
97
- accli jobs -p my-project -n 50
98
- accli jobs -s failed # graphs with a failed job
99
- accli jobs -s processing,error --all
96
+ accli jobs my-project # latest 10
97
+ accli jobs my-project -n 50
98
+ accli jobs my-project -s failed # graphs with a failed job
99
+ accli jobs my-project -s processing,error
100
100
  ```
101
101
 
102
102
  `--status` filters on the graph status: `processing` (or `active`), `error` (or `failed`) and `done`. A standalone job counts by its own status.
103
103
 
104
- Show a job's status and details. For a job holding children or callbacks, Graph status covers the jobs below it (the same as GRAPH in `accli jobs`) and each one is listed; any job also links to the pipeline it is part of. Read a job's output with `accli logs <job>`:
104
+ Show a job's status and details. For a job holding children or callbacks, Graph status covers the jobs below it (the same as GRAPH in `accli jobs`) and each one is listed; any job also links to the pipeline it is part of. Read a job's output with `accli logs <project> <job>`:
105
105
 
106
106
  ```bash
107
- accli job 1234
108
- accli job 1234 --json
107
+ accli job my-project 1234
108
+ accli job my-project 1234 --json
109
109
  ```
110
110
 
111
111
  ---
@@ -1,3 +1,3 @@
1
1
  # Please strictly put double quote to use this info for git tag
2
2
  # Read a developer guide for git tag command with regex
3
- VERSION = "v2.1.0"
3
+ VERSION = "v2.1.1"
@@ -228,9 +228,8 @@ LOG_POLL_SECONDS = 5
228
228
 
229
229
  @app.command()
230
230
  def logs(
231
+ project_slug: Annotated[str, typer.Argument(help="Unique Accelerator project slug.")],
231
232
  job_id: Annotated[int, typer.Argument(help="Job ID.")],
232
- project_slug: Annotated[
233
- str, typer.Option(..., '-p', help="Project slug. Defaults to the last project used.")] = None,
234
233
  follow: Annotated[
235
234
  bool, typer.Option(..., '--follow', '-f', help="Keep printing new lines until the job finishes.")] = False,
236
235
  source: Annotated[
@@ -241,17 +240,7 @@ def logs(
241
240
  show_time: Annotated[bool, typer.Option('--time/--no-time', help="Show the time of each line.")] = True,
242
241
  ):
243
242
  """Print a job's merged log: executor output, scheduler output and cluster events."""
244
- project_slug = project_slug or get_project_slug()
245
- if not project_slug:
246
- print("[bold red]ERROR: No project selected. Pass one with -p.[/bold red]")
247
- raise typer.Exit(1)
248
-
249
- _, access_token, _ = exchange_refresh_token(project_slug)
250
- service = AcceleratorTerminalCliProjectService(
251
- user_token=access_token,
252
- server_url=get_server_url(),
253
- verify_cert=(not ACCLI_DEBUG)
254
- )
243
+ service = _cli_service_for(project_slug)
255
244
  sources = ",".join(source) if source else None
256
245
  limit = 100 if whole else None
257
246
 
@@ -297,19 +286,16 @@ def logs(
297
286
 
298
287
 
299
288
  def _cli_service_for(project_slug):
300
- """Resolve the project (or the last one used) and return (slug, service)."""
301
- project_slug = project_slug or get_project_slug()
302
- if not project_slug:
303
- print("[bold red]ERROR: No project selected. Pass one with -p.[/bold red]")
304
- raise typer.Exit(1)
305
-
289
+ """Check the project exists and is accessible (as `mount` does), then return a CLI service for it."""
290
+ server_url = get_server_url()
291
+ validate_project_slug(server_url, project_slug)
292
+ # The token from validation is cached in memory, so this exchange makes no extra request.
306
293
  _, access_token, _ = exchange_refresh_token(project_slug)
307
- service = AcceleratorTerminalCliProjectService(
294
+ return AcceleratorTerminalCliProjectService(
308
295
  user_token=access_token,
309
- server_url=get_server_url(),
296
+ server_url=server_url,
310
297
  verify_cert=(not ACCLI_DEBUG)
311
298
  )
312
- return project_slug, service
313
299
 
314
300
 
315
301
  def _api_error_detail(err):
@@ -321,8 +307,7 @@ def _api_error_detail(err):
321
307
 
322
308
  @app.command()
323
309
  def jobs(
324
- project_slug: Annotated[
325
- str, typer.Option(..., '-p', help="Project slug. Defaults to the last project used.")] = None,
310
+ project_slug: Annotated[str, typer.Argument(help="Unique Accelerator project slug.")],
326
311
  status: Annotated[
327
312
  List[str], typer.Option(
328
313
  ..., '--status', '-s',
@@ -330,35 +315,25 @@ def jobs(
330
315
  "Only jobs whose graph has this status: processing (or active), error (or failed), done. "
331
316
  "A standalone job counts by its own status. Repeatable or comma separated."
332
317
  ))] = None,
333
- limit: Annotated[int, typer.Option(..., '--limit', '-n', help="How many jobs to show.")] = 10,
334
- whole: Annotated[bool, typer.Option(..., '--all', help="Show every matching job, not only the latest.")] = False,
318
+ limit: Annotated[
319
+ int, typer.Option(..., '--limit', '-n', min=1, max=200, help="How many jobs to show, up to 200.")] = 10,
335
320
  as_json: Annotated[bool, typer.Option(..., '--json', help="Print the raw JSON.")] = False,
336
321
  ):
337
322
  """List root jobs (newest first) with their type and status.
338
323
 
339
324
  TYPE is standalone, children, callback or children+callback. GRAPH is the status
340
325
  of everything below the root job, and what --status filters on. See a job's
341
- status and everything it holds with `accli job <job>`.
326
+ status and everything it holds with `accli job <project> <job>`.
342
327
  """
343
- project_slug, service = _cli_service_for(project_slug)
328
+ service = _cli_service_for(project_slug)
344
329
  status_filter = ",".join(t.strip() for s in (status or []) for t in s.split(",") if t.strip()) or None
345
- page_size = 10 if whole else min(limit, 200)
346
330
 
347
- results, count, cursor = [], 0, None
348
331
  try:
349
- while True:
350
- page = service.list_root_jobs(project_slug, limit=page_size, cursor=cursor, status=status_filter)
351
- results += page["results"]
352
- count = page["count"]
353
- cursor = page.get("next_cursor")
354
- if not cursor or (not whole and len(results) >= limit):
355
- break
332
+ page = service.list_root_jobs(project_slug, limit=limit, status=status_filter)
356
333
  except AccAPIError as err:
357
334
  print(f"[bold red]ERROR: {_api_error_detail(err)}[/bold red]")
358
335
  raise typer.Exit(1)
359
-
360
- if not whole:
361
- results = results[:limit]
336
+ results, count = page["results"], page["count"]
362
337
 
363
338
  if as_json:
364
339
  typer.echo(json.dumps(dict(count=count, results=results), indent=2, default=str))
@@ -370,22 +345,21 @@ def jobs(
370
345
 
371
346
  print(job_views.root_jobs_table(results))
372
347
  if count > len(results):
373
- print(f"[dim]Showing {len(results)} of {count} jobs. Use -n or --all to see more.[/dim]")
348
+ print(f"[dim]Showing {len(results)} of {count} jobs. Use -n to see more.[/dim]")
374
349
 
375
350
 
376
351
  @app.command()
377
352
  def job(
353
+ project_slug: Annotated[str, typer.Argument(help="Unique Accelerator project slug.")],
378
354
  job_id: Annotated[int, typer.Argument(help="Job ID: a single job, or one holding children and callbacks.")],
379
- project_slug: Annotated[
380
- str, typer.Option(..., '-p', help="Project slug. Defaults to the last project used.")] = None,
381
355
  as_json: Annotated[bool, typer.Option(..., '--json', help="Print the raw JSON.")] = False,
382
356
  ):
383
357
  """Show a job's status and details. For a job holding children or callbacks, Graph status
384
358
  covers the jobs below it, the same as GRAPH in `accli jobs`, and each one is listed.
385
359
 
386
- See a job's output with `accli logs <job>`.
360
+ See a job's output with `accli logs <project> <job>`.
387
361
  """
388
- project_slug, service = _cli_service_for(project_slug)
362
+ service = _cli_service_for(project_slug)
389
363
  try:
390
364
  job_graph = service.get_job_graph(project_slug, job_id)
391
365
  except AccAPIError as err:
@@ -396,7 +370,7 @@ def job(
396
370
  typer.echo(json.dumps(job_graph, indent=2, default=str))
397
371
  return
398
372
 
399
- title, details = job_views.job_details(job_graph, job_id)
373
+ title, details = job_views.job_details(job_graph, job_id, project_slug)
400
374
  print(title)
401
375
  print(details)
402
376
  if len(job_views.subtree(job_graph, job_id)) > 1:
@@ -272,11 +272,12 @@ class AccliGuiApp(tk.Tk):
272
272
  self.server_entry.configure(state=tk.NORMAL)
273
273
  self.status_bar_val.set("Session Status: Disconnected")
274
274
 
275
+ debug_status = "Enabled ($env:ACCLI_DEBUG='1')" if os.environ.get("ACCLI_DEBUG") else "Disabled"
275
276
  details = [
276
277
  f"Server URL: {server_url}",
277
278
  f"Session Status: {'Connected / Logged In' if logged_in else 'Not Logged In'}",
278
279
  f"Token DB Path: {db_path}",
279
- f"Debug Mode: {'Enabled ($env:ACCLI_DEBUG=\"1\")' if os.environ.get('ACCLI_DEBUG') else 'Disabled'}"
280
+ f"Debug Mode: {debug_status}"
280
281
  ]
281
282
  self.conn_details_txt.configure(state=tk.NORMAL)
282
283
  self.conn_details_txt.delete("1.0", tk.END)
@@ -170,7 +170,7 @@ def _own_status(job):
170
170
  return text
171
171
 
172
172
 
173
- def job_details(graph, job_id):
173
+ def job_details(graph, job_id, project_slug):
174
174
  """Key/value block for one job. Graph status covers the jobs below it, when it has any."""
175
175
  job = next(j for j in graph["jobs"] if j["id"] == job_id)
176
176
  jobs = subtree(graph, job_id)
@@ -199,10 +199,10 @@ def job_details(graph, job_id):
199
199
  part_of.append(" ")
200
200
  part_of.append_text(status_text(graph["graph_status"]))
201
201
  part_of.append(f" {len(graph['jobs'])} jobs ", style="dim")
202
- part_of.append(f"accli job {root['id']}", style="cyan")
202
+ part_of.append(f"accli job {project_slug} {root['id']}", style="cyan")
203
203
  table.add_row("Part of", part_of)
204
204
 
205
- table.add_row("Logs", Text(f"accli logs {job['id']}", style="cyan"))
205
+ table.add_row("Logs", Text(f"accli logs {project_slug} {job['id']}", style="cyan"))
206
206
 
207
207
  title = Text()
208
208
  title.append(f"Job #{job['id']}", style="bold")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: accli
3
- Version: 2.1.0
3
+ Version: 2.1.1
4
4
  Summary: IIASA Accelerator Client
5
5
  Author-email: Wrufesh S <wrufesh@gmail.com>
6
6
  License: The MIT License (MIT)
@@ -29,7 +29,7 @@ Project-URL: Bug Tracker, https://github.com/iiasa/accli/issues
29
29
  Classifier: Programming Language :: Python :: 3
30
30
  Classifier: License :: OSI Approved :: MIT License
31
31
  Classifier: Operating System :: OS Independent
32
- Requires-Python: >=3.7
32
+ Requires-Python: >=3.8
33
33
  Description-Content-Type: text/markdown
34
34
  License-File: LICENSE
35
35
  Requires-Dist: urllib3>=1.26.5
@@ -137,19 +137,19 @@ Usage: accli [OPTIONS] COMMAND [ARGS]...
137
137
  List root jobs, newest first, with each job's type (`standalone`, `children`, `callback` or `children+callback`), its own status and the status of its whole graph:
138
138
 
139
139
  ```bash
140
- accli jobs # latest 10 in the last project used
141
- accli jobs -p my-project -n 50
142
- accli jobs -s failed # graphs with a failed job
143
- accli jobs -s processing,error --all
140
+ accli jobs my-project # latest 10
141
+ accli jobs my-project -n 50
142
+ accli jobs my-project -s failed # graphs with a failed job
143
+ accli jobs my-project -s processing,error
144
144
  ```
145
145
 
146
146
  `--status` filters on the graph status: `processing` (or `active`), `error` (or `failed`) and `done`. A standalone job counts by its own status.
147
147
 
148
- Show a job's status and details. For a job holding children or callbacks, Graph status covers the jobs below it (the same as GRAPH in `accli jobs`) and each one is listed; any job also links to the pipeline it is part of. Read a job's output with `accli logs <job>`:
148
+ Show a job's status and details. For a job holding children or callbacks, Graph status covers the jobs below it (the same as GRAPH in `accli jobs`) and each one is listed; any job also links to the pipeline it is part of. Read a job's output with `accli logs <project> <job>`:
149
149
 
150
150
  ```bash
151
- accli job 1234
152
- accli job 1234 --json
151
+ accli job my-project 1234
152
+ accli job my-project 1234 --json
153
153
  ```
154
154
 
155
155
  ---
@@ -11,7 +11,7 @@ authors = [
11
11
  description = "IIASA Accelerator Client"
12
12
  readme = "README.md"
13
13
  license = {file = "LICENSE"}
14
- requires-python = ">=3.7"
14
+ requires-python = ">=3.8"
15
15
  dependencies = [
16
16
  "urllib3>=1.26.5", # 2.0.7 is recent, but 1.26.5+ is widely compatible
17
17
  "typer[all]>=0.4.0", # 0.4.0+ is stable and has most features
@@ -1,6 +1,8 @@
1
1
  """`accli jobs` and `accli job` against a fake API."""
2
2
  import json
3
3
 
4
+ import pytest
5
+
4
6
  from typer.testing import CliRunner
5
7
 
6
8
  from accli import cli
@@ -31,11 +33,12 @@ class FakeService:
31
33
  return self.graph
32
34
 
33
35
 
34
- def run(monkeypatch, service, *args, exit_code=0):
36
+ def run(monkeypatch, service, command, *args, exit_code=0):
35
37
  monkeypatch.setattr(cli, "exchange_refresh_token", lambda slug: ("", "token", 0))
38
+ monkeypatch.setattr(cli, "validate_project_slug", lambda server_url, slug: None)
36
39
  monkeypatch.setattr(cli, "get_server_url", lambda: "https://acc.example")
37
40
  monkeypatch.setattr(cli, "AcceleratorTerminalCliProjectService", lambda **kw: service)
38
- result = CliRunner().invoke(cli.app, [*args, "-p", "demo"], env={"COLUMNS": "200"})
41
+ result = CliRunner().invoke(cli.app, [command, "demo", *args], env={"COLUMNS": "200"})
39
42
  assert result.exit_code == exit_code, result.output
40
43
  return result.output
41
44
 
@@ -48,7 +51,7 @@ def test_jobs_lists_types_and_joins_status_filters(monkeypatch):
48
51
  ])])
49
52
  output = run(monkeypatch, service, "jobs", "-s", "error,processing", "--status", "done", "-n", "3")
50
53
 
51
- assert service.calls == [dict(limit=3, cursor=None, status="error,processing,done")]
54
+ assert service.calls == [dict(limit=3, status="error,processing,done")]
52
55
  rows = {line.split()[0]: line for line in output.splitlines() if line.strip()[:1].isdigit()}
53
56
  assert "children+callback" in rows["3"] and "PROCESSING" in rows["3"]
54
57
  assert "standalone" in rows["2"] and "ERROR" in rows["2"]
@@ -56,15 +59,17 @@ def test_jobs_lists_types_and_joins_status_filters(monkeypatch):
56
59
  assert "Showing" not in output
57
60
 
58
61
 
59
- def test_jobs_all_follows_cursor(monkeypatch):
60
- service = FakeService([
61
- dict(count=2, next_cursor="c1", results=[job(2, "b")]),
62
- dict(count=2, next_cursor=None, results=[job(1, "a")]),
63
- ])
64
- output = json.loads(run(monkeypatch, service, "jobs", "--all", "--json"))
62
+ def test_jobs_fetches_one_page_and_points_to_n(monkeypatch):
63
+ service = FakeService([dict(count=25, next_cursor="c1", results=[job(2, "b"), job(1, "a")])])
64
+ output = run(monkeypatch, service, "jobs")
65
+
66
+ assert service.calls == [dict(limit=10, status=None)]
67
+ assert "Showing 2 of 25 jobs. Use -n to see more." in output
65
68
 
66
- assert [j["id"] for j in output["results"]] == [2, 1]
67
- assert service.calls[1]["cursor"] == "c1"
69
+
70
+ def test_jobs_requires_project(monkeypatch):
71
+ result = CliRunner().invoke(cli.app, ["jobs"])
72
+ assert result.exit_code != 0 and "Missing argument 'project_slug'" in result.output
68
73
 
69
74
 
70
75
  def test_jobs_shows_api_error(monkeypatch):
@@ -96,8 +101,8 @@ def test_leaf_job_shows_only_its_own_status(monkeypatch):
96
101
  assert output.startswith("Job #13 after a")
97
102
  assert "Status DONE" in output and "Graph status" not in output
98
103
  assert "callback after #11" in output
99
- assert "Part of #10 root ERROR 5 jobs accli job 10" in output
100
- assert "accli logs 13" in output
104
+ assert "Part of #10 root ERROR 5 jobs accli job demo 10" in output
105
+ assert "accli logs demo 13" in output
101
106
  assert "Jobs in" not in output
102
107
 
103
108
 
@@ -138,3 +143,18 @@ def test_graph_status_leaves_out_the_job_itself(monkeypatch):
138
143
  output = run(monkeypatch, FakeService(graph=graph), "job", "1")
139
144
  assert "Status KILLED" in output
140
145
  assert "Graph status DONE 1 jobs below: 1 DONE" in output
146
+
147
+
148
+ def test_commands_validate_the_project_first(monkeypatch):
149
+ checked = []
150
+
151
+ def validate(server_url, slug):
152
+ checked.append((server_url, slug))
153
+ raise cli.typer.Exit(1)
154
+
155
+ monkeypatch.setattr(cli, "get_server_url", lambda: "https://acc.example")
156
+ monkeypatch.setattr(cli, "validate_project_slug", validate)
157
+ monkeypatch.setattr(cli, "AcceleratorTerminalCliProjectService", lambda **kw: pytest.fail("API called"))
158
+ for args in (["jobs", "nope"], ["job", "nope", "1"], ["logs", "nope", "1"]):
159
+ assert CliRunner().invoke(cli.app, args).exit_code == 1
160
+ assert checked == [("https://acc.example", "nope")] * 3
@@ -36,10 +36,11 @@ def chunk(seq, url, end):
36
36
  def run(monkeypatch, pages, *args):
37
37
  service = FakeService(pages)
38
38
  monkeypatch.setattr(cli, "exchange_refresh_token", lambda slug: ("", "token", 0))
39
+ monkeypatch.setattr(cli, "validate_project_slug", lambda server_url, slug: None)
39
40
  monkeypatch.setattr(cli, "get_server_url", lambda: "https://acc.example")
40
41
  monkeypatch.setattr(cli, "AcceleratorTerminalCliProjectService", lambda **kw: service)
41
42
  monkeypatch.setattr(cli.time, "sleep", lambda s: None)
42
- result = CliRunner().invoke(cli.app, ["logs", "42", "-p", "demo", "--no-time", *args])
43
+ result = CliRunner().invoke(cli.app, ["logs", "demo", "42", "--no-time", *args])
43
44
  assert result.exit_code == 0, result.output
44
45
  return result.output.splitlines(), service
45
46
 
@@ -65,3 +66,4 @@ def test_without_follow_prints_partial_line_once(monkeypatch):
65
66
 
66
67
  assert [line.split(None, 1)[1] for line in output] == ["starting", "downloading 4"]
67
68
  assert len(service.calls) == 1
69
+
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes