sfapi-client 0.6.0__tar.gz → 0.7.0__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.
- {sfapi_client-0.6.0 → sfapi_client-0.7.0}/.github/workflows/pytest.yml +1 -1
- {sfapi_client-0.6.0 → sfapi_client-0.7.0}/PKG-INFO +5 -3
- {sfapi_client-0.6.0 → sfapi_client-0.7.0}/README.md +2 -0
- {sfapi_client-0.6.0 → sfapi_client-0.7.0}/mkdocs.yml +0 -2
- {sfapi_client-0.6.0 → sfapi_client-0.7.0}/pyproject.toml +4 -1
- {sfapi_client-0.6.0 → sfapi_client-0.7.0}/src/sfapi_client/_async/compute.py +11 -1
- {sfapi_client-0.6.0 → sfapi_client-0.7.0}/src/sfapi_client/_sync/compute.py +17 -4
- {sfapi_client-0.6.0 → sfapi_client-0.7.0}/src/sfapi_client/_sync/paths.py +1 -1
- {sfapi_client-0.6.0 → sfapi_client-0.7.0}/tests/test_compute.py +27 -0
- {sfapi_client-0.6.0 → sfapi_client-0.7.0}/tests/test_compute_async.py +28 -0
- {sfapi_client-0.6.0 → sfapi_client-0.7.0}/.github/dependabot.yml +0 -0
- {sfapi_client-0.6.0 → sfapi_client-0.7.0}/.github/workflows/black.yml +0 -0
- {sfapi_client-0.6.0 → sfapi_client-0.7.0}/.github/workflows/mkdocs.yml +0 -0
- {sfapi_client-0.6.0 → sfapi_client-0.7.0}/.github/workflows/pypi.yml +0 -0
- {sfapi_client-0.6.0 → sfapi_client-0.7.0}/.github/workflows/ruff.yml +0 -0
- {sfapi_client-0.6.0 → sfapi_client-0.7.0}/.gitignore +0 -0
- {sfapi_client-0.6.0 → sfapi_client-0.7.0}/.pre-commit-config.yaml +0 -0
- {sfapi_client-0.6.0 → sfapi_client-0.7.0}/LEGAL +0 -0
- {sfapi_client-0.6.0 → sfapi_client-0.7.0}/LICENSE +0 -0
- {sfapi_client-0.6.0 → sfapi_client-0.7.0}/docs/community/contributing.md +0 -0
- {sfapi_client-0.6.0 → sfapi_client-0.7.0}/docs/examples/check_current_and_past_jobs.ipynb +0 -0
- {sfapi_client-0.6.0 → sfapi_client-0.7.0}/docs/examples/getting_bearer_token.ipynb +0 -0
- {sfapi_client-0.6.0 → sfapi_client-0.7.0}/docs/examples/run_job_and_check_status.ipynb +0 -0
- {sfapi_client-0.6.0 → sfapi_client-0.7.0}/docs/gen_examples.py +0 -0
- {sfapi_client-0.6.0 → sfapi_client-0.7.0}/docs/gen_sync.py +0 -0
- {sfapi_client-0.6.0 → sfapi_client-0.7.0}/docs/index.md +0 -0
- {sfapi_client-0.6.0 → sfapi_client-0.7.0}/docs/javascript/apiselector.js +0 -0
- {sfapi_client-0.6.0 → sfapi_client-0.7.0}/docs/quickstart/index.md +0 -0
- {sfapi_client-0.6.0 → sfapi_client-0.7.0}/docs/reference/SUMMARY.txt +0 -0
- {sfapi_client-0.6.0 → sfapi_client-0.7.0}/docs/reference/async/client/index.md +0 -0
- {sfapi_client-0.6.0 → sfapi_client-0.7.0}/docs/reference/async/compute/index.md +0 -0
- {sfapi_client-0.6.0 → sfapi_client-0.7.0}/docs/reference/async/exceptions/index.md +0 -0
- {sfapi_client-0.6.0 → sfapi_client-0.7.0}/docs/reference/async/groups/index.md +0 -0
- {sfapi_client-0.6.0 → sfapi_client-0.7.0}/docs/reference/async/jobs/index.md +0 -0
- {sfapi_client-0.6.0 → sfapi_client-0.7.0}/docs/reference/async/paths/index.md +0 -0
- {sfapi_client-0.6.0 → sfapi_client-0.7.0}/docs/reference/async/projects/index.md +0 -0
- {sfapi_client-0.6.0 → sfapi_client-0.7.0}/docs/reference/async/resources/index.md +0 -0
- {sfapi_client-0.6.0 → sfapi_client-0.7.0}/docs/reference/async/users/index.md +0 -0
- {sfapi_client-0.6.0 → sfapi_client-0.7.0}/pytest.ini +0 -0
- {sfapi_client-0.6.0 → sfapi_client-0.7.0}/requirements-dev.txt +0 -0
- {sfapi_client-0.6.0 → sfapi_client-0.7.0}/scripts/run.py +0 -0
- {sfapi_client-0.6.0 → sfapi_client-0.7.0}/src/sfapi_client/__init__.py +0 -0
- {sfapi_client-0.6.0 → sfapi_client-0.7.0}/src/sfapi_client/_async/__init__.py +0 -0
- {sfapi_client-0.6.0 → sfapi_client-0.7.0}/src/sfapi_client/_async/client.py +0 -0
- {sfapi_client-0.6.0 → sfapi_client-0.7.0}/src/sfapi_client/_async/groups.py +0 -0
- {sfapi_client-0.6.0 → sfapi_client-0.7.0}/src/sfapi_client/_async/jobs.py +0 -0
- {sfapi_client-0.6.0 → sfapi_client-0.7.0}/src/sfapi_client/_async/paths.py +0 -0
- {sfapi_client-0.6.0 → sfapi_client-0.7.0}/src/sfapi_client/_async/projects.py +0 -0
- {sfapi_client-0.6.0 → sfapi_client-0.7.0}/src/sfapi_client/_async/storage.py +0 -0
- {sfapi_client-0.6.0 → sfapi_client-0.7.0}/src/sfapi_client/_async/users.py +0 -0
- {sfapi_client-0.6.0 → sfapi_client-0.7.0}/src/sfapi_client/_compute.py +0 -0
- {sfapi_client-0.6.0 → sfapi_client-0.7.0}/src/sfapi_client/_jobs.py +0 -0
- {sfapi_client-0.6.0 → sfapi_client-0.7.0}/src/sfapi_client/_models/__init__.py +0 -0
- {sfapi_client-0.6.0 → sfapi_client-0.7.0}/src/sfapi_client/_models/job_status_response_sacct.py +0 -0
- {sfapi_client-0.6.0 → sfapi_client-0.7.0}/src/sfapi_client/_models/job_status_response_squeue.py +0 -0
- {sfapi_client-0.6.0 → sfapi_client-0.7.0}/src/sfapi_client/_models/resources.py +0 -0
- {sfapi_client-0.6.0 → sfapi_client-0.7.0}/src/sfapi_client/_monitor.py +0 -0
- {sfapi_client-0.6.0 → sfapi_client-0.7.0}/src/sfapi_client/_sync/__init__.py +0 -0
- {sfapi_client-0.6.0 → sfapi_client-0.7.0}/src/sfapi_client/_sync/client.py +0 -0
- {sfapi_client-0.6.0 → sfapi_client-0.7.0}/src/sfapi_client/_sync/groups.py +0 -0
- {sfapi_client-0.6.0 → sfapi_client-0.7.0}/src/sfapi_client/_sync/jobs.py +0 -0
- {sfapi_client-0.6.0 → sfapi_client-0.7.0}/src/sfapi_client/_sync/projects.py +0 -0
- {sfapi_client-0.6.0 → sfapi_client-0.7.0}/src/sfapi_client/_sync/storage.py +0 -0
- {sfapi_client-0.6.0 → sfapi_client-0.7.0}/src/sfapi_client/_sync/users.py +0 -0
- {sfapi_client-0.6.0 → sfapi_client-0.7.0}/src/sfapi_client/_utils.py +0 -0
- {sfapi_client-0.6.0 → sfapi_client-0.7.0}/src/sfapi_client/client.py +0 -0
- {sfapi_client-0.6.0 → sfapi_client-0.7.0}/src/sfapi_client/compute.py +0 -0
- {sfapi_client-0.6.0 → sfapi_client-0.7.0}/src/sfapi_client/exceptions.py +0 -0
- {sfapi_client-0.6.0 → sfapi_client-0.7.0}/src/sfapi_client/groups.py +0 -0
- {sfapi_client-0.6.0 → sfapi_client-0.7.0}/src/sfapi_client/jobs.py +0 -0
- {sfapi_client-0.6.0 → sfapi_client-0.7.0}/src/sfapi_client/paths.py +0 -0
- {sfapi_client-0.6.0 → sfapi_client-0.7.0}/src/sfapi_client/projects.py +0 -0
- {sfapi_client-0.6.0 → sfapi_client-0.7.0}/src/sfapi_client/resources.py +0 -0
- {sfapi_client-0.6.0 → sfapi_client-0.7.0}/src/sfapi_client/storage.py +0 -0
- {sfapi_client-0.6.0 → sfapi_client-0.7.0}/src/sfapi_client/users.py +0 -0
- {sfapi_client-0.6.0 → sfapi_client-0.7.0}/tests/conftest.py +0 -0
- {sfapi_client-0.6.0 → sfapi_client-0.7.0}/tests/test_api.py +0 -0
- {sfapi_client-0.6.0 → sfapi_client-0.7.0}/tests/test_api_async.py +0 -0
- {sfapi_client-0.6.0 → sfapi_client-0.7.0}/tests/test_client.py +0 -0
- {sfapi_client-0.6.0 → sfapi_client-0.7.0}/tests/test_client_async.py +0 -0
- {sfapi_client-0.6.0 → sfapi_client-0.7.0}/tests/test_groups.py +0 -0
- {sfapi_client-0.6.0 → sfapi_client-0.7.0}/tests/test_groups_async.py +0 -0
- {sfapi_client-0.6.0 → sfapi_client-0.7.0}/tests/test_jobs.py +0 -0
- {sfapi_client-0.6.0 → sfapi_client-0.7.0}/tests/test_jobs_async.py +0 -0
- {sfapi_client-0.6.0 → sfapi_client-0.7.0}/tests/test_key.py +0 -0
- {sfapi_client-0.6.0 → sfapi_client-0.7.0}/tests/test_paths.py +0 -0
- {sfapi_client-0.6.0 → sfapi_client-0.7.0}/tests/test_paths_async.py +0 -0
- {sfapi_client-0.6.0 → sfapi_client-0.7.0}/tests/test_projects.py +0 -0
- {sfapi_client-0.6.0 → sfapi_client-0.7.0}/tests/test_projects_async.py +0 -0
- {sfapi_client-0.6.0 → sfapi_client-0.7.0}/tests/test_resources.py +0 -0
- {sfapi_client-0.6.0 → sfapi_client-0.7.0}/tests/test_resources_async.py +0 -0
- {sfapi_client-0.6.0 → sfapi_client-0.7.0}/tests/test_storage.py +0 -0
- {sfapi_client-0.6.0 → sfapi_client-0.7.0}/tests/test_storage_async.py +0 -0
- {sfapi_client-0.6.0 → sfapi_client-0.7.0}/tests/test_users.py +0 -0
- {sfapi_client-0.6.0 → sfapi_client-0.7.0}/tests/test_users_async.py +0 -0
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
2
|
Name: sfapi_client
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.7.0
|
|
4
4
|
Summary: Python client for NERSC SF API
|
|
5
5
|
Author-email: Chris Harris <cjh@lbl.gov>, Nicholas Tyler <tylern@lbl.gov>
|
|
6
6
|
License-File: LICENSE
|
|
7
7
|
Classifier: License :: OSI Approved :: BSD License
|
|
8
8
|
Classifier: Operating System :: OS Independent
|
|
9
9
|
Classifier: Programming Language :: Python :: 3
|
|
10
|
-
Requires-Python: >=3.
|
|
10
|
+
Requires-Python: >=3.10
|
|
11
11
|
Requires-Dist: authlib
|
|
12
12
|
Requires-Dist: httpx
|
|
13
13
|
Requires-Dist: pydantic-settings
|
|
@@ -76,6 +76,8 @@ The sfapi_client project relies on these libraries:
|
|
|
76
76
|
|
|
77
77
|
## Installation
|
|
78
78
|
|
|
79
|
+
Requires Python 3.10 or newer.
|
|
80
|
+
|
|
79
81
|
Install with pip:
|
|
80
82
|
|
|
81
83
|
```shell
|
|
@@ -14,7 +14,7 @@ authors = [
|
|
|
14
14
|
]
|
|
15
15
|
description = "Python client for NERSC SF API"
|
|
16
16
|
readme = "README.md"
|
|
17
|
-
requires-python = ">=3.
|
|
17
|
+
requires-python = ">=3.10"
|
|
18
18
|
dependencies = [
|
|
19
19
|
"authlib",
|
|
20
20
|
"httpx",
|
|
@@ -43,6 +43,9 @@ exclude = [
|
|
|
43
43
|
"src/sfapi_client/_async/_models.py"
|
|
44
44
|
]
|
|
45
45
|
|
|
46
|
+
[tool.ruff.lint]
|
|
47
|
+
select = ["E4", "E7", "E9", "F"]
|
|
48
|
+
|
|
46
49
|
[project.optional-dependencies]
|
|
47
50
|
docs = [
|
|
48
51
|
"mkdocs-material",
|
|
@@ -224,7 +224,17 @@ class AsyncCompute(ComputeBase):
|
|
|
224
224
|
task = await self._run_task(args)
|
|
225
225
|
await task._wait()
|
|
226
226
|
|
|
227
|
-
|
|
227
|
+
# A non-empty `error` (stderr) alone doesn't mean the command failed - many commands
|
|
228
|
+
# write non-fatal warnings to stderr while still exiting zero. `exit_code` is the
|
|
229
|
+
# authoritative success/failure signal when the API provides it; only fall back to
|
|
230
|
+
# treating a non-empty `error` as fatal if `exit_code` is unavailable.
|
|
231
|
+
if task.result.exit_code is not None:
|
|
232
|
+
if task.result.exit_code != 0:
|
|
233
|
+
raise SfApiError(
|
|
234
|
+
task.result.error
|
|
235
|
+
or f"Command exited with code {task.result.exit_code}"
|
|
236
|
+
)
|
|
237
|
+
elif task.result.error:
|
|
228
238
|
raise SfApiError(task.result.error)
|
|
229
239
|
|
|
230
240
|
return task.result.output
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
from typing import Dict, List, Optional, Union
|
|
2
|
-
import json
|
|
3
2
|
from pydantic import PrivateAttr, ConfigDict, BaseModel
|
|
4
3
|
from ..exceptions import SfApiError
|
|
5
4
|
from .._utils import _SLEEP
|
|
@@ -127,7 +126,9 @@ class Compute(ComputeBase):
|
|
|
127
126
|
return task.result
|
|
128
127
|
|
|
129
128
|
@check_auth
|
|
130
|
-
def submit_job(
|
|
129
|
+
def submit_job(
|
|
130
|
+
self, script: Union[str, RemotePath], args: Optional[List[str]] = None
|
|
131
|
+
) -> JobSqueue:
|
|
131
132
|
"""Submit a job to the compute resource
|
|
132
133
|
|
|
133
134
|
:param script: Path to file on the compute system, or script to run beginning with `#!`.
|
|
@@ -154,7 +155,9 @@ class Compute(ComputeBase):
|
|
|
154
155
|
data = {"job": script, "isPath": is_path}
|
|
155
156
|
if args is not None:
|
|
156
157
|
if not is_path:
|
|
157
|
-
raise ValueError(
|
|
158
|
+
raise ValueError(
|
|
159
|
+
"Command line arguments cannot be passed when the script is not a file."
|
|
160
|
+
)
|
|
158
161
|
data["args"] = args
|
|
159
162
|
|
|
160
163
|
r = self.client.post(f"compute/jobs/{self.name}", data)
|
|
@@ -221,7 +224,17 @@ class Compute(ComputeBase):
|
|
|
221
224
|
task = self._run_task(args)
|
|
222
225
|
task._wait()
|
|
223
226
|
|
|
224
|
-
|
|
227
|
+
# A non-empty `error` (stderr) alone doesn't mean the command failed - many commands
|
|
228
|
+
# write non-fatal warnings to stderr while still exiting zero. `exit_code` is the
|
|
229
|
+
# authoritative success/failure signal when the API provides it; only fall back to
|
|
230
|
+
# treating a non-empty `error` as fatal if `exit_code` is unavailable.
|
|
231
|
+
if task.result.exit_code is not None:
|
|
232
|
+
if task.result.exit_code != 0:
|
|
233
|
+
raise SfApiError(
|
|
234
|
+
task.result.error
|
|
235
|
+
or f"Command exited with code {task.result.exit_code}"
|
|
236
|
+
)
|
|
237
|
+
elif task.result.error:
|
|
225
238
|
raise SfApiError(task.result.error)
|
|
226
239
|
|
|
227
240
|
return task.result.output
|
|
@@ -162,7 +162,7 @@ class RemotePath(PathBase):
|
|
|
162
162
|
|
|
163
163
|
r = self.compute.client.get(
|
|
164
164
|
f"utilities/download/{self.compute.name}/{self._path}",
|
|
165
|
-
params={"binary": binary}
|
|
165
|
+
params={"binary": binary},
|
|
166
166
|
)
|
|
167
167
|
json_response = r.json()
|
|
168
168
|
download_response = FileDownloadResponse.model_validate(json_response)
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import time
|
|
2
2
|
from pathlib import Path
|
|
3
3
|
|
|
4
|
+
import pytest
|
|
5
|
+
|
|
6
|
+
from sfapi_client import SfApiError
|
|
4
7
|
from sfapi_client.jobs import JobState
|
|
5
8
|
from sfapi_client.compute import CommandTask, TaskStatus
|
|
6
9
|
|
|
@@ -125,6 +128,30 @@ def test_run_arg_path(authenticated_client, test_machine):
|
|
|
125
128
|
assert output
|
|
126
129
|
|
|
127
130
|
|
|
131
|
+
def test_run_success_with_stderr(authenticated_client, test_machine):
|
|
132
|
+
with authenticated_client as client:
|
|
133
|
+
machine = client.compute(test_machine)
|
|
134
|
+
output = machine.run("bash -lc 'echo hello; echo warning >&2; exit 0'")
|
|
135
|
+
|
|
136
|
+
assert output == "hello\n"
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def test_run_failure_with_stderr(authenticated_client, test_machine):
|
|
140
|
+
with authenticated_client as client:
|
|
141
|
+
machine = client.compute(test_machine)
|
|
142
|
+
|
|
143
|
+
with pytest.raises(SfApiError, match="failing"):
|
|
144
|
+
machine.run(_failing_command())
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def test_run_failure_without_stderr(authenticated_client, test_machine):
|
|
148
|
+
with authenticated_client as client:
|
|
149
|
+
machine = client.compute(test_machine)
|
|
150
|
+
|
|
151
|
+
with pytest.raises(SfApiError, match="Command exited with code 7"):
|
|
152
|
+
machine.run("bash -lc 'exit 7'")
|
|
153
|
+
|
|
154
|
+
|
|
128
155
|
def test_run_task(authenticated_client, test_machine):
|
|
129
156
|
with authenticated_client as client:
|
|
130
157
|
machine = client.compute(test_machine)
|
|
@@ -3,6 +3,7 @@ import asyncio
|
|
|
3
3
|
import pytest
|
|
4
4
|
from pathlib import Path
|
|
5
5
|
|
|
6
|
+
from sfapi_client import SfApiError
|
|
6
7
|
from sfapi_client.compute import AsyncCommandTask, TaskStatus
|
|
7
8
|
from sfapi_client.jobs import JobState
|
|
8
9
|
|
|
@@ -140,6 +141,33 @@ async def test_run_arg_path(async_authenticated_client, test_machine):
|
|
|
140
141
|
assert output
|
|
141
142
|
|
|
142
143
|
|
|
144
|
+
@pytest.mark.asyncio
|
|
145
|
+
async def test_run_success_with_stderr(async_authenticated_client, test_machine):
|
|
146
|
+
async with async_authenticated_client as client:
|
|
147
|
+
machine = await client.compute(test_machine)
|
|
148
|
+
output = await machine.run("bash -lc 'echo hello; echo warning >&2; exit 0'")
|
|
149
|
+
|
|
150
|
+
assert output == "hello\n"
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
@pytest.mark.asyncio
|
|
154
|
+
async def test_run_failure_with_stderr(async_authenticated_client, test_machine):
|
|
155
|
+
async with async_authenticated_client as client:
|
|
156
|
+
machine = await client.compute(test_machine)
|
|
157
|
+
|
|
158
|
+
with pytest.raises(SfApiError, match="failing"):
|
|
159
|
+
await machine.run(_failing_command())
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
@pytest.mark.asyncio
|
|
163
|
+
async def test_run_failure_without_stderr(async_authenticated_client, test_machine):
|
|
164
|
+
async with async_authenticated_client as client:
|
|
165
|
+
machine = await client.compute(test_machine)
|
|
166
|
+
|
|
167
|
+
with pytest.raises(SfApiError, match="Command exited with code 7"):
|
|
168
|
+
await machine.run("bash -lc 'exit 7'")
|
|
169
|
+
|
|
170
|
+
|
|
143
171
|
@pytest.mark.asyncio
|
|
144
172
|
async def test_run_task(async_authenticated_client, test_machine):
|
|
145
173
|
async with async_authenticated_client as client:
|
|
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
|
|
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
|
|
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
|
{sfapi_client-0.6.0 → sfapi_client-0.7.0}/src/sfapi_client/_models/job_status_response_sacct.py
RENAMED
|
File without changes
|
{sfapi_client-0.6.0 → sfapi_client-0.7.0}/src/sfapi_client/_models/job_status_response_squeue.py
RENAMED
|
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
|
|
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
|
|
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
|