sfapi-client 0.0.8__tar.gz → 0.1.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.0.8 → sfapi_client-0.1.0}/.github/workflows/black.yml +1 -1
- {sfapi_client-0.0.8 → sfapi_client-0.1.0}/.github/workflows/mkdocs.yml +3 -3
- {sfapi_client-0.0.8 → sfapi_client-0.1.0}/.github/workflows/pypi.yml +4 -4
- {sfapi_client-0.0.8 → sfapi_client-0.1.0}/.github/workflows/pytest.yml +2 -2
- {sfapi_client-0.0.8 → sfapi_client-0.1.0}/.github/workflows/ruff.yml +1 -1
- {sfapi_client-0.0.8 → sfapi_client-0.1.0}/PKG-INFO +2 -2
- {sfapi_client-0.0.8 → sfapi_client-0.1.0}/requirements-dev.txt +1 -0
- {sfapi_client-0.0.8 → sfapi_client-0.1.0}/src/sfapi_client/_async/client.py +7 -0
- {sfapi_client-0.0.8 → sfapi_client-0.1.0}/src/sfapi_client/_async/paths.py +4 -1
- {sfapi_client-0.0.8 → sfapi_client-0.1.0}/src/sfapi_client/_async/users.py +17 -1
- {sfapi_client-0.0.8 → sfapi_client-0.1.0}/src/sfapi_client/_models/__init__.py +34 -7
- {sfapi_client-0.0.8 → sfapi_client-0.1.0}/src/sfapi_client/_sync/client.py +6 -0
- {sfapi_client-0.0.8 → sfapi_client-0.1.0}/src/sfapi_client/_sync/users.py +17 -1
- {sfapi_client-0.0.8 → sfapi_client-0.1.0}/tests/conftest.py +43 -0
- {sfapi_client-0.0.8 → sfapi_client-0.1.0}/tests/test_api.py +4 -5
- {sfapi_client-0.0.8 → sfapi_client-0.1.0}/tests/test_api_async.py +4 -6
- sfapi_client-0.1.0/tests/test_client.py +17 -0
- sfapi_client-0.1.0/tests/test_client_async.py +19 -0
- {sfapi_client-0.0.8 → sfapi_client-0.1.0}/tests/test_compute.py +18 -19
- {sfapi_client-0.0.8 → sfapi_client-0.1.0}/tests/test_compute_async.py +24 -23
- {sfapi_client-0.0.8 → sfapi_client-0.1.0}/tests/test_groups_async.py +2 -5
- {sfapi_client-0.0.8 → sfapi_client-0.1.0}/tests/test_jobs.py +10 -10
- {sfapi_client-0.0.8 → sfapi_client-0.1.0}/tests/test_jobs_async.py +16 -16
- {sfapi_client-0.0.8 → sfapi_client-0.1.0}/tests/test_paths.py +22 -23
- {sfapi_client-0.0.8 → sfapi_client-0.1.0}/tests/test_paths_async.py +28 -28
- sfapi_client-0.1.0/tests/test_projects.py +12 -0
- sfapi_client-0.1.0/tests/test_projects_async.py +17 -0
- {sfapi_client-0.0.8 → sfapi_client-0.1.0}/tests/test_resources.py +19 -19
- {sfapi_client-0.0.8 → sfapi_client-0.1.0}/tests/test_resources_async.py +21 -19
- {sfapi_client-0.0.8 → sfapi_client-0.1.0}/tests/test_users.py +28 -17
- {sfapi_client-0.0.8 → sfapi_client-0.1.0}/tests/test_users_async.py +29 -13
- sfapi_client-0.0.8/tests/test_client.py +0 -17
- sfapi_client-0.0.8/tests/test_client_async.py +0 -19
- sfapi_client-0.0.8/tests/test_projects.py +0 -15
- sfapi_client-0.0.8/tests/test_projects_async.py +0 -19
- {sfapi_client-0.0.8 → sfapi_client-0.1.0}/.github/dependabot.yml +0 -0
- {sfapi_client-0.0.8 → sfapi_client-0.1.0}/.gitignore +0 -0
- {sfapi_client-0.0.8 → sfapi_client-0.1.0}/.pre-commit-config.yaml +0 -0
- {sfapi_client-0.0.8 → sfapi_client-0.1.0}/LEGAL +0 -0
- {sfapi_client-0.0.8 → sfapi_client-0.1.0}/LICENSE +0 -0
- {sfapi_client-0.0.8 → sfapi_client-0.1.0}/README.md +0 -0
- {sfapi_client-0.0.8 → sfapi_client-0.1.0}/docs/community/contributing.md +0 -0
- {sfapi_client-0.0.8 → sfapi_client-0.1.0}/docs/examples/check_current_and_past_jobs.ipynb +0 -0
- {sfapi_client-0.0.8 → sfapi_client-0.1.0}/docs/examples/getting_bearer_token.ipynb +0 -0
- {sfapi_client-0.0.8 → sfapi_client-0.1.0}/docs/examples/run_job_and_check_status.ipynb +0 -0
- {sfapi_client-0.0.8 → sfapi_client-0.1.0}/docs/gen_examples.py +0 -0
- {sfapi_client-0.0.8 → sfapi_client-0.1.0}/docs/gen_sync.py +0 -0
- {sfapi_client-0.0.8 → sfapi_client-0.1.0}/docs/index.md +0 -0
- {sfapi_client-0.0.8 → sfapi_client-0.1.0}/docs/javascript/apiselector.js +0 -0
- {sfapi_client-0.0.8 → sfapi_client-0.1.0}/docs/quickstart/index.md +0 -0
- {sfapi_client-0.0.8 → sfapi_client-0.1.0}/docs/reference/SUMMARY.txt +0 -0
- {sfapi_client-0.0.8 → sfapi_client-0.1.0}/docs/reference/async/client/index.md +0 -0
- {sfapi_client-0.0.8 → sfapi_client-0.1.0}/docs/reference/async/compute/index.md +0 -0
- {sfapi_client-0.0.8 → sfapi_client-0.1.0}/docs/reference/async/exceptions/index.md +0 -0
- {sfapi_client-0.0.8 → sfapi_client-0.1.0}/docs/reference/async/groups/index.md +0 -0
- {sfapi_client-0.0.8 → sfapi_client-0.1.0}/docs/reference/async/jobs/index.md +0 -0
- {sfapi_client-0.0.8 → sfapi_client-0.1.0}/docs/reference/async/paths/index.md +0 -0
- {sfapi_client-0.0.8 → sfapi_client-0.1.0}/docs/reference/async/projects/index.md +0 -0
- {sfapi_client-0.0.8 → sfapi_client-0.1.0}/docs/reference/async/resources/index.md +0 -0
- {sfapi_client-0.0.8 → sfapi_client-0.1.0}/docs/reference/async/users/index.md +0 -0
- {sfapi_client-0.0.8 → sfapi_client-0.1.0}/mkdocs.yml +0 -0
- {sfapi_client-0.0.8 → sfapi_client-0.1.0}/pyproject.toml +0 -0
- {sfapi_client-0.0.8 → sfapi_client-0.1.0}/pytest.ini +0 -0
- {sfapi_client-0.0.8 → sfapi_client-0.1.0}/scripts/run.py +0 -0
- {sfapi_client-0.0.8 → sfapi_client-0.1.0}/src/sfapi_client/__init__.py +0 -0
- {sfapi_client-0.0.8 → sfapi_client-0.1.0}/src/sfapi_client/_async/__init__.py +0 -0
- {sfapi_client-0.0.8 → sfapi_client-0.1.0}/src/sfapi_client/_async/compute.py +0 -0
- {sfapi_client-0.0.8 → sfapi_client-0.1.0}/src/sfapi_client/_async/groups.py +0 -0
- {sfapi_client-0.0.8 → sfapi_client-0.1.0}/src/sfapi_client/_async/jobs.py +0 -0
- {sfapi_client-0.0.8 → sfapi_client-0.1.0}/src/sfapi_client/_async/projects.py +0 -0
- {sfapi_client-0.0.8 → sfapi_client-0.1.0}/src/sfapi_client/_compute.py +0 -0
- {sfapi_client-0.0.8 → sfapi_client-0.1.0}/src/sfapi_client/_jobs.py +0 -0
- {sfapi_client-0.0.8 → sfapi_client-0.1.0}/src/sfapi_client/_models/job_status_response_sacct.py +0 -0
- {sfapi_client-0.0.8 → sfapi_client-0.1.0}/src/sfapi_client/_models/job_status_response_squeue.py +0 -0
- {sfapi_client-0.0.8 → sfapi_client-0.1.0}/src/sfapi_client/_models/resources.py +0 -0
- {sfapi_client-0.0.8 → sfapi_client-0.1.0}/src/sfapi_client/_monitor.py +0 -0
- {sfapi_client-0.0.8 → sfapi_client-0.1.0}/src/sfapi_client/_sync/__init__.py +0 -0
- {sfapi_client-0.0.8 → sfapi_client-0.1.0}/src/sfapi_client/_sync/compute.py +0 -0
- {sfapi_client-0.0.8 → sfapi_client-0.1.0}/src/sfapi_client/_sync/groups.py +0 -0
- {sfapi_client-0.0.8 → sfapi_client-0.1.0}/src/sfapi_client/_sync/jobs.py +0 -0
- {sfapi_client-0.0.8 → sfapi_client-0.1.0}/src/sfapi_client/_sync/paths.py +0 -0
- {sfapi_client-0.0.8 → sfapi_client-0.1.0}/src/sfapi_client/_sync/projects.py +0 -0
- {sfapi_client-0.0.8 → sfapi_client-0.1.0}/src/sfapi_client/_utils.py +0 -0
- {sfapi_client-0.0.8 → sfapi_client-0.1.0}/src/sfapi_client/client.py +0 -0
- {sfapi_client-0.0.8 → sfapi_client-0.1.0}/src/sfapi_client/compute.py +0 -0
- {sfapi_client-0.0.8 → sfapi_client-0.1.0}/src/sfapi_client/exceptions.py +0 -0
- {sfapi_client-0.0.8 → sfapi_client-0.1.0}/src/sfapi_client/groups.py +0 -0
- {sfapi_client-0.0.8 → sfapi_client-0.1.0}/src/sfapi_client/jobs.py +0 -0
- {sfapi_client-0.0.8 → sfapi_client-0.1.0}/src/sfapi_client/paths.py +0 -0
- {sfapi_client-0.0.8 → sfapi_client-0.1.0}/src/sfapi_client/projects.py +0 -0
- {sfapi_client-0.0.8 → sfapi_client-0.1.0}/src/sfapi_client/resources.py +0 -0
- {sfapi_client-0.0.8 → sfapi_client-0.1.0}/src/sfapi_client/users.py +0 -0
- {sfapi_client-0.0.8 → sfapi_client-0.1.0}/tests/test_groups.py +0 -0
|
@@ -12,11 +12,11 @@ jobs:
|
|
|
12
12
|
runs-on: ubuntu-latest
|
|
13
13
|
if: github.event_name == 'push' && startsWith(github.event.ref, 'refs/tags/')
|
|
14
14
|
steps:
|
|
15
|
-
- uses: actions/checkout@
|
|
16
|
-
- uses: actions/setup-python@
|
|
15
|
+
- uses: actions/checkout@v4
|
|
16
|
+
- uses: actions/setup-python@v5
|
|
17
17
|
with:
|
|
18
18
|
python-version: 3.x
|
|
19
|
-
- uses: actions/cache@
|
|
19
|
+
- uses: actions/cache@v4
|
|
20
20
|
with:
|
|
21
21
|
key: mkdocs-${{ github.ref }}
|
|
22
22
|
path: .cache
|
|
@@ -13,9 +13,9 @@ jobs:
|
|
|
13
13
|
name: Build wheel
|
|
14
14
|
runs-on: ubuntu-latest
|
|
15
15
|
steps:
|
|
16
|
-
- uses: actions/checkout@
|
|
16
|
+
- uses: actions/checkout@v4
|
|
17
17
|
- name: Set up Python
|
|
18
|
-
uses: actions/setup-python@
|
|
18
|
+
uses: actions/setup-python@v5
|
|
19
19
|
with:
|
|
20
20
|
python-version: "3.x"
|
|
21
21
|
- name: Install pypa/build
|
|
@@ -33,7 +33,7 @@ jobs:
|
|
|
33
33
|
--wheel
|
|
34
34
|
--outdir dist/
|
|
35
35
|
|
|
36
|
-
- uses: actions/upload-artifact@
|
|
36
|
+
- uses: actions/upload-artifact@v4
|
|
37
37
|
with:
|
|
38
38
|
name: wheel
|
|
39
39
|
path: ./dist/
|
|
@@ -49,7 +49,7 @@ jobs:
|
|
|
49
49
|
id-token: write
|
|
50
50
|
if: github.event_name == 'push' && startsWith(github.event.ref, 'refs/tags/')
|
|
51
51
|
steps:
|
|
52
|
-
- uses: actions/download-artifact@
|
|
52
|
+
- uses: actions/download-artifact@v4
|
|
53
53
|
with:
|
|
54
54
|
name: wheel
|
|
55
55
|
path: dist
|
|
@@ -16,9 +16,9 @@ jobs:
|
|
|
16
16
|
python-version: ["3.8", "3.9", "3.10", "3.11"]
|
|
17
17
|
|
|
18
18
|
steps:
|
|
19
|
-
- uses: actions/checkout@
|
|
19
|
+
- uses: actions/checkout@v4
|
|
20
20
|
- name: Set up Python ${{ matrix.python-version }}
|
|
21
|
-
uses: actions/setup-python@
|
|
21
|
+
uses: actions/setup-python@v5
|
|
22
22
|
with:
|
|
23
23
|
python-version: ${{ matrix.python-version }}
|
|
24
24
|
- name: Install dependencies
|
|
@@ -512,3 +512,10 @@ AsyncProject.model_rebuild()
|
|
|
512
512
|
AsyncRemotePath.model_rebuild()
|
|
513
513
|
AsyncRole.model_rebuild()
|
|
514
514
|
AsyncGroupMember.model_rebuild()
|
|
515
|
+
|
|
516
|
+
# Ensure that the job models are built, we need to import here to
|
|
517
|
+
# avoid circular imports
|
|
518
|
+
from .jobs import AsyncJobSacct, AsyncJobSqueue # noqa: E402
|
|
519
|
+
|
|
520
|
+
AsyncJobSqueue.model_rebuild()
|
|
521
|
+
AsyncJobSacct.model_rebuild()
|
|
@@ -165,7 +165,10 @@ class AsyncRemotePath(PathBase):
|
|
|
165
165
|
|
|
166
166
|
@staticmethod
|
|
167
167
|
async def _ls(
|
|
168
|
-
compute: "
|
|
168
|
+
compute: "AsyncCompute", # noqa: F821
|
|
169
|
+
path,
|
|
170
|
+
directory=False,
|
|
171
|
+
filter_dots=True, # noqa: F821
|
|
169
172
|
) -> List["RemotePath"]: # noqa: F821
|
|
170
173
|
r = await compute.client.get(f"utilities/ls/{compute.name}/{path}")
|
|
171
174
|
|
|
@@ -6,6 +6,7 @@ from pydantic import ConfigDict
|
|
|
6
6
|
from .._models import (
|
|
7
7
|
UserInfo as UserBase,
|
|
8
8
|
GroupList as GroupsResponse,
|
|
9
|
+
Client as APIClient,
|
|
9
10
|
)
|
|
10
11
|
from .projects import AsyncProject, AsyncRole
|
|
11
12
|
from ..exceptions import SfApiError
|
|
@@ -31,7 +32,8 @@ class AsyncUser(UserBase):
|
|
|
31
32
|
@staticmethod
|
|
32
33
|
@check_auth
|
|
33
34
|
async def _fetch_user(
|
|
34
|
-
client: "AsyncClient",
|
|
35
|
+
client: "AsyncClient", # noqa: F821
|
|
36
|
+
username: Optional[str] = None, # noqa: F821
|
|
35
37
|
): # noqa: F821
|
|
36
38
|
url = "account/"
|
|
37
39
|
if username is not None:
|
|
@@ -106,3 +108,17 @@ class AsyncUser(UserBase):
|
|
|
106
108
|
]
|
|
107
109
|
|
|
108
110
|
return roles
|
|
111
|
+
|
|
112
|
+
async def clients(self) -> List[APIClient]:
|
|
113
|
+
"""
|
|
114
|
+
Get information about the authenticated user's SFAPI clients.
|
|
115
|
+
|
|
116
|
+
:return: the api clients
|
|
117
|
+
"""
|
|
118
|
+
r = await self.client.get("account/clients")
|
|
119
|
+
|
|
120
|
+
json_response = r.json()
|
|
121
|
+
|
|
122
|
+
clients = [APIClient.model_validate(c) for c in json_response]
|
|
123
|
+
|
|
124
|
+
return clients
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
# generated by datamodel-codegen:
|
|
2
|
-
# filename: https://api.nersc.gov/api/v1.2/openapi.json
|
|
3
|
-
# timestamp:
|
|
2
|
+
# filename: https://api-dev.nersc.gov/api/v1.2/openapi.json
|
|
3
|
+
# timestamp: 2024-07-18T15:58:27+00:00
|
|
4
4
|
|
|
5
5
|
from __future__ import annotations
|
|
6
6
|
|
|
7
7
|
from datetime import date as date_, datetime
|
|
8
8
|
from enum import Enum
|
|
9
|
-
from typing import Dict, List, Optional
|
|
9
|
+
from typing import Any, Dict, List, Optional, Union
|
|
10
10
|
|
|
11
11
|
from pydantic import BaseModel, Field
|
|
12
12
|
|
|
@@ -37,6 +37,9 @@ class BodySubmitJobComputeJobsMachinePost(BaseModel):
|
|
|
37
37
|
isPath: bool = Field(..., title="Ispath")
|
|
38
38
|
job: str = Field(..., title="Job")
|
|
39
39
|
args: Optional[List[str]] = Field([], title="Args")
|
|
40
|
+
callbackTimeout: Optional[int] = Field(None, title="Callbacktimeout")
|
|
41
|
+
callbackEmail: Optional[str] = Field(None, title="Callbackemail")
|
|
42
|
+
callbackUrl: Optional[str] = Field(None, title="Callbackurl")
|
|
40
43
|
|
|
41
44
|
|
|
42
45
|
class BodyUpdateGroupMembershipAccountGroupsGroupPut(BaseModel):
|
|
@@ -53,6 +56,13 @@ class Changelog(BaseModel):
|
|
|
53
56
|
change: Optional[str] = Field(None, title="Change")
|
|
54
57
|
|
|
55
58
|
|
|
59
|
+
class Client(BaseModel):
|
|
60
|
+
name: str = Field(..., title="Name")
|
|
61
|
+
clientId: str = Field(..., title="Clientid")
|
|
62
|
+
expiresAt: str = Field(..., title="Expiresat")
|
|
63
|
+
comments: Optional[str] = Field(None, title="Comments")
|
|
64
|
+
|
|
65
|
+
|
|
56
66
|
class Config(BaseModel):
|
|
57
67
|
key: str = Field(..., title="Key")
|
|
58
68
|
value: Optional[str] = Field(None, title="Value")
|
|
@@ -88,6 +98,7 @@ class Outage(BaseModel):
|
|
|
88
98
|
|
|
89
99
|
class PublicHost(str, Enum):
|
|
90
100
|
dtn01 = "dtn01"
|
|
101
|
+
dtns = "dtns"
|
|
91
102
|
perlmutter = "perlmutter"
|
|
92
103
|
|
|
93
104
|
|
|
@@ -117,8 +128,8 @@ class StorageStats(BaseModel):
|
|
|
117
128
|
|
|
118
129
|
class Task(BaseModel):
|
|
119
130
|
id: str = Field(..., title="Id")
|
|
120
|
-
status: Optional[str] = Field(
|
|
121
|
-
result: Optional[str] = Field(
|
|
131
|
+
status: Optional[str] = Field(..., title="Status")
|
|
132
|
+
result: Optional[str] = Field(..., title="Result")
|
|
122
133
|
|
|
123
134
|
|
|
124
135
|
class Tasks(BaseModel):
|
|
@@ -142,7 +153,7 @@ class UserStats(BaseModel):
|
|
|
142
153
|
|
|
143
154
|
|
|
144
155
|
class ValidationError(BaseModel):
|
|
145
|
-
loc: List[str] = Field(..., title="Location")
|
|
156
|
+
loc: List[Union[str, int]] = Field(..., title="Location")
|
|
146
157
|
msg: str = Field(..., title="Message")
|
|
147
158
|
type: str = Field(..., title="Error Type")
|
|
148
159
|
|
|
@@ -212,6 +223,22 @@ class ProjectStats(BaseModel):
|
|
|
212
223
|
hours_used: Optional[float] = Field(None, title="Hours Used")
|
|
213
224
|
project_hours_given: Optional[float] = Field(None, title="Project Hours Given")
|
|
214
225
|
project_hours_used: Optional[float] = Field(None, title="Project Hours Used")
|
|
226
|
+
cpu_hours_given: Optional[float] = Field(None, title="Cpu Hours Given")
|
|
227
|
+
cpu_hours_used: Optional[float] = Field(None, title="Cpu Hours Used")
|
|
228
|
+
cpu_project_hours_given: Optional[float] = Field(
|
|
229
|
+
None, title="Cpu Project Hours Given"
|
|
230
|
+
)
|
|
231
|
+
cpu_project_hours_used: Optional[float] = Field(
|
|
232
|
+
None, title="Cpu Project Hours Used"
|
|
233
|
+
)
|
|
234
|
+
gpu_hours_given: Optional[float] = Field(None, title="Gpu Hours Given")
|
|
235
|
+
gpu_hours_used: Optional[float] = Field(None, title="Gpu Hours Used")
|
|
236
|
+
gpu_project_hours_given: Optional[float] = Field(
|
|
237
|
+
None, title="Gpu Project Hours Given"
|
|
238
|
+
)
|
|
239
|
+
gpu_project_hours_used: Optional[float] = Field(
|
|
240
|
+
None, title="Gpu Project Hours Used"
|
|
241
|
+
)
|
|
215
242
|
projdir_usage: Optional[List[StorageStats]] = Field(None, title="Projdir Usage")
|
|
216
243
|
project_projdir_usage: Optional[StorageStats] = None
|
|
217
244
|
hpss_usage: Optional[List[StorageStats]] = Field(None, title="Hpss Usage")
|
|
@@ -226,7 +253,7 @@ class QueueOutput(BaseModel):
|
|
|
226
253
|
class TransferResult(BaseModel):
|
|
227
254
|
task_id: str = Field(..., title="Task Id")
|
|
228
255
|
status: AppRoutersStorageModelsStatus
|
|
229
|
-
reason: Optional[str] = Field(
|
|
256
|
+
reason: Optional[str] = Field(..., title="Reason")
|
|
230
257
|
|
|
231
258
|
|
|
232
259
|
class UploadResult(BaseModel):
|
|
@@ -512,3 +512,9 @@ Project.model_rebuild()
|
|
|
512
512
|
RemotePath.model_rebuild()
|
|
513
513
|
Role.model_rebuild()
|
|
514
514
|
GroupMember.model_rebuild()
|
|
515
|
+
|
|
516
|
+
# Ensure that the job models are built, we need to import here to
|
|
517
|
+
# avoid circular imports
|
|
518
|
+
from .jobs import JobSacct, JobSqueue
|
|
519
|
+
JobSqueue.model_rebuild()
|
|
520
|
+
JobSacct.model_rebuild()
|
|
@@ -6,6 +6,7 @@ from pydantic import ConfigDict
|
|
|
6
6
|
from .._models import (
|
|
7
7
|
UserInfo as UserBase,
|
|
8
8
|
GroupList as GroupsResponse,
|
|
9
|
+
Client as APIClient,
|
|
9
10
|
)
|
|
10
11
|
from .projects import Project, Role
|
|
11
12
|
from ..exceptions import SfApiError
|
|
@@ -31,7 +32,8 @@ class User(UserBase):
|
|
|
31
32
|
@staticmethod
|
|
32
33
|
@check_auth
|
|
33
34
|
def _fetch_user(
|
|
34
|
-
client: "Client",
|
|
35
|
+
client: "Client", # noqa: F821
|
|
36
|
+
username: Optional[str] = None, # noqa: F821
|
|
35
37
|
): # noqa: F821
|
|
36
38
|
url = "account/"
|
|
37
39
|
if username is not None:
|
|
@@ -106,3 +108,17 @@ class User(UserBase):
|
|
|
106
108
|
]
|
|
107
109
|
|
|
108
110
|
return roles
|
|
111
|
+
|
|
112
|
+
def clients(self) -> List[APIClient]:
|
|
113
|
+
"""
|
|
114
|
+
Get information about the authenticated user's SFAPI clients.
|
|
115
|
+
|
|
116
|
+
:return: the api clients
|
|
117
|
+
"""
|
|
118
|
+
r = self.client.get("account/clients")
|
|
119
|
+
|
|
120
|
+
json_response = r.json()
|
|
121
|
+
|
|
122
|
+
clients = [APIClient.model_validate(c) for c in json_response]
|
|
123
|
+
|
|
124
|
+
return clients
|
|
@@ -8,12 +8,15 @@ from authlib.jose import JsonWebKey
|
|
|
8
8
|
|
|
9
9
|
from sfapi_client.compute import Machine
|
|
10
10
|
from sfapi_client import Resource
|
|
11
|
+
from sfapi_client import Client, AsyncClient
|
|
11
12
|
|
|
12
13
|
from pydantic import ConfigDict
|
|
13
14
|
from pydantic_settings import BaseSettings
|
|
14
15
|
|
|
15
16
|
|
|
16
17
|
class Settings(BaseSettings):
|
|
18
|
+
API_BASE_URL: str = "https://api.nersc.gov/api/v1.2"
|
|
19
|
+
TOKEN_URL: str = "https://oidc.nersc.gov/c2id/token"
|
|
17
20
|
SFAPI_CLIENT_ID: Optional[str] = None
|
|
18
21
|
SFAPI_CLIENT_SECRET: Optional[Union[str, Dict]] = None
|
|
19
22
|
SFAPI_DEV_CLIENT_ID: Optional[str] = None
|
|
@@ -125,3 +128,43 @@ def dev_api_url():
|
|
|
125
128
|
@pytest.fixture
|
|
126
129
|
def dev_token_url():
|
|
127
130
|
return settings.DEV_TOKEN_URL
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
@pytest.fixture
|
|
134
|
+
def api_base_url():
|
|
135
|
+
return settings.API_BASE_URL
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
@pytest.fixture
|
|
139
|
+
def token_url():
|
|
140
|
+
return settings.TOKEN_URL
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
@pytest.fixture
|
|
144
|
+
def unauthenticated_client(api_base_url):
|
|
145
|
+
return Client(api_base_url=api_base_url)
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
@pytest.fixture
|
|
149
|
+
def async_unauthenticated_client(api_base_url):
|
|
150
|
+
return AsyncClient(api_base_url=api_base_url)
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
@pytest.fixture
|
|
154
|
+
def authenticated_client(api_base_url, token_url, client_id, client_secret):
|
|
155
|
+
return Client(
|
|
156
|
+
api_base_url=api_base_url,
|
|
157
|
+
token_url=token_url,
|
|
158
|
+
client_id=client_id,
|
|
159
|
+
secret=client_secret,
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
@pytest.fixture
|
|
164
|
+
def async_authenticated_client(api_base_url, token_url, client_id, client_secret):
|
|
165
|
+
return AsyncClient(
|
|
166
|
+
api_base_url=api_base_url,
|
|
167
|
+
token_url=token_url,
|
|
168
|
+
client_id=client_id,
|
|
169
|
+
secret=client_secret,
|
|
170
|
+
)
|
|
@@ -1,16 +1,15 @@
|
|
|
1
1
|
import pytest
|
|
2
|
-
from sfapi_client import Client
|
|
3
2
|
|
|
4
3
|
|
|
5
4
|
@pytest.mark.public
|
|
6
|
-
def test_changelog():
|
|
7
|
-
with
|
|
5
|
+
def test_changelog(unauthenticated_client):
|
|
6
|
+
with unauthenticated_client as client:
|
|
8
7
|
changelog = client.api.changelog()
|
|
9
8
|
assert len(changelog) > 0
|
|
10
9
|
|
|
11
10
|
|
|
12
11
|
@pytest.mark.public
|
|
13
|
-
def test_config():
|
|
14
|
-
with
|
|
12
|
+
def test_config(unauthenticated_client):
|
|
13
|
+
with unauthenticated_client as client:
|
|
15
14
|
config = client.api.config()
|
|
16
15
|
assert "compute.targets" in config
|
|
@@ -1,19 +1,17 @@
|
|
|
1
1
|
import pytest
|
|
2
2
|
|
|
3
|
-
from sfapi_client import AsyncClient
|
|
4
|
-
|
|
5
3
|
|
|
6
4
|
@pytest.mark.public
|
|
7
5
|
@pytest.mark.asyncio
|
|
8
|
-
async def test_changelog():
|
|
9
|
-
async with
|
|
6
|
+
async def test_changelog(async_unauthenticated_client):
|
|
7
|
+
async with async_unauthenticated_client as client:
|
|
10
8
|
changelog = await client.api.changelog()
|
|
11
9
|
assert len(changelog) > 0
|
|
12
10
|
|
|
13
11
|
|
|
14
12
|
@pytest.mark.public
|
|
15
13
|
@pytest.mark.asyncio
|
|
16
|
-
async def test_config():
|
|
17
|
-
async with
|
|
14
|
+
async def test_config(async_unauthenticated_client):
|
|
15
|
+
async with async_unauthenticated_client as client:
|
|
18
16
|
config = await client.api.config()
|
|
19
17
|
assert "compute.targets" in config
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
|
|
3
|
+
from sfapi_client import SfApiError
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
@pytest.mark.public
|
|
7
|
+
def test_no_creds(unauthenticated_client):
|
|
8
|
+
with unauthenticated_client as client:
|
|
9
|
+
assert client is not None
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@pytest.mark.public
|
|
13
|
+
def test_no_creds_auth_required(unauthenticated_client, test_machine):
|
|
14
|
+
with unauthenticated_client as client:
|
|
15
|
+
machine = client.compute(test_machine)
|
|
16
|
+
with pytest.raises(SfApiError):
|
|
17
|
+
machine.jobs()
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
|
|
3
|
+
from sfapi_client import SfApiError
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
@pytest.mark.public
|
|
7
|
+
@pytest.mark.asyncio
|
|
8
|
+
async def test_no_creds(async_unauthenticated_client):
|
|
9
|
+
async with async_unauthenticated_client as client:
|
|
10
|
+
assert client is not None
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@pytest.mark.public
|
|
14
|
+
@pytest.mark.asyncio
|
|
15
|
+
async def test_no_creds_auth_required(async_unauthenticated_client, test_machine):
|
|
16
|
+
async with async_unauthenticated_client as client:
|
|
17
|
+
machine = await client.compute(test_machine)
|
|
18
|
+
with pytest.raises(SfApiError):
|
|
19
|
+
await machine.jobs()
|
|
@@ -1,18 +1,17 @@
|
|
|
1
1
|
from pathlib import Path
|
|
2
2
|
|
|
3
|
-
from sfapi_client import Client
|
|
4
3
|
from sfapi_client.jobs import JobState
|
|
5
4
|
|
|
6
5
|
|
|
7
|
-
def test_compute(
|
|
8
|
-
with
|
|
6
|
+
def test_compute(authenticated_client, test_machine):
|
|
7
|
+
with authenticated_client as client:
|
|
9
8
|
machine = client.compute(test_machine)
|
|
10
9
|
assert machine.status in ["active", "unavailable", "degraded", "other"]
|
|
11
10
|
assert machine.name == test_machine.value
|
|
12
11
|
|
|
13
12
|
|
|
14
|
-
def test_job(
|
|
15
|
-
with
|
|
13
|
+
def test_job(authenticated_client, test_job_path, test_machine):
|
|
14
|
+
with authenticated_client as client:
|
|
16
15
|
machine = client.compute(test_machine)
|
|
17
16
|
|
|
18
17
|
job = machine.submit_job(test_job_path)
|
|
@@ -24,14 +23,14 @@ def test_job(client_id, client_secret, test_job_path, test_machine):
|
|
|
24
23
|
assert job.jobid == job_looked_up.jobid
|
|
25
24
|
|
|
26
25
|
|
|
27
|
-
def test_fetch_jobs(
|
|
28
|
-
with
|
|
26
|
+
def test_fetch_jobs(authenticated_client, test_machine, test_username):
|
|
27
|
+
with authenticated_client as client:
|
|
29
28
|
machine = client.compute(test_machine)
|
|
30
29
|
machine.jobs(user=test_username)
|
|
31
30
|
|
|
32
31
|
|
|
33
|
-
def test_list_dir_contents(
|
|
34
|
-
with
|
|
32
|
+
def test_list_dir_contents(authenticated_client, test_machine, test_job_path):
|
|
33
|
+
with authenticated_client as client:
|
|
35
34
|
machine = client.compute(test_machine)
|
|
36
35
|
test_job = Path(test_job_path)
|
|
37
36
|
test_path = test_job.parent
|
|
@@ -48,8 +47,8 @@ def test_list_dir_contents(client_id, client_secret, test_machine, test_job_path
|
|
|
48
47
|
assert found
|
|
49
48
|
|
|
50
49
|
|
|
51
|
-
def test_list_dir(
|
|
52
|
-
with
|
|
50
|
+
def test_list_dir(authenticated_client, test_machine, test_job_path):
|
|
51
|
+
with authenticated_client as client:
|
|
53
52
|
machine = client.compute(test_machine)
|
|
54
53
|
test_job = Path(test_job_path)
|
|
55
54
|
test_path = test_job.parent
|
|
@@ -59,8 +58,8 @@ def test_list_dir(client_id, client_secret, test_machine, test_job_path):
|
|
|
59
58
|
assert paths[0].name == test_path.name
|
|
60
59
|
|
|
61
60
|
|
|
62
|
-
def test_list_file(
|
|
63
|
-
with
|
|
61
|
+
def test_list_file(authenticated_client, test_machine, test_job_path):
|
|
62
|
+
with authenticated_client as client:
|
|
64
63
|
machine = client.compute(test_machine)
|
|
65
64
|
test_job_filename = Path(test_job_path).name
|
|
66
65
|
|
|
@@ -75,24 +74,24 @@ def test_list_file(client_id, client_secret, test_machine, test_job_path):
|
|
|
75
74
|
assert found
|
|
76
75
|
|
|
77
76
|
|
|
78
|
-
def test_run_arg_list(
|
|
79
|
-
with
|
|
77
|
+
def test_run_arg_list(authenticated_client, test_machine, test_job_path):
|
|
78
|
+
with authenticated_client as client:
|
|
80
79
|
machine = client.compute(test_machine)
|
|
81
80
|
output = machine.run(["ls", test_job_path])
|
|
82
81
|
|
|
83
82
|
assert test_job_path in output
|
|
84
83
|
|
|
85
84
|
|
|
86
|
-
def test_run_arg_str(
|
|
87
|
-
with
|
|
85
|
+
def test_run_arg_str(authenticated_client, test_machine, test_job_path):
|
|
86
|
+
with authenticated_client as client:
|
|
88
87
|
machine = client.compute(test_machine)
|
|
89
88
|
output = machine.run(f"ls {test_job_path}")
|
|
90
89
|
|
|
91
90
|
assert test_job_path in output
|
|
92
91
|
|
|
93
92
|
|
|
94
|
-
def test_run_arg_path(
|
|
95
|
-
with
|
|
93
|
+
def test_run_arg_path(authenticated_client, test_machine):
|
|
94
|
+
with authenticated_client as client:
|
|
96
95
|
machine = client.compute(test_machine)
|
|
97
96
|
[remote_path] = machine.ls("/usr/bin/hostname")
|
|
98
97
|
output = machine.run(remote_path)
|
|
@@ -1,21 +1,20 @@
|
|
|
1
1
|
import pytest
|
|
2
2
|
from pathlib import Path
|
|
3
3
|
|
|
4
|
-
from sfapi_client import AsyncClient
|
|
5
4
|
from sfapi_client.jobs import JobState
|
|
6
5
|
|
|
7
6
|
|
|
8
7
|
@pytest.mark.asyncio
|
|
9
|
-
async def test_compute(
|
|
10
|
-
async with
|
|
8
|
+
async def test_compute(async_authenticated_client, test_machine):
|
|
9
|
+
async with async_authenticated_client as client:
|
|
11
10
|
machine = await client.compute(test_machine)
|
|
12
11
|
assert machine.status in ["active", "unavailable", "degraded", "other"]
|
|
13
12
|
assert machine.name == test_machine.value
|
|
14
13
|
|
|
15
14
|
|
|
16
15
|
@pytest.mark.asyncio
|
|
17
|
-
async def test_job(
|
|
18
|
-
async with
|
|
16
|
+
async def test_job(async_authenticated_client, test_job_path, test_machine):
|
|
17
|
+
async with async_authenticated_client as client:
|
|
19
18
|
machine = await client.compute(test_machine)
|
|
20
19
|
|
|
21
20
|
job = await machine.submit_job(test_job_path)
|
|
@@ -28,15 +27,17 @@ async def test_job(client_id, client_secret, test_job_path, test_machine):
|
|
|
28
27
|
|
|
29
28
|
|
|
30
29
|
@pytest.mark.asyncio
|
|
31
|
-
async def test_fetch_jobs(
|
|
32
|
-
async with
|
|
30
|
+
async def test_fetch_jobs(async_authenticated_client, test_machine, test_username):
|
|
31
|
+
async with async_authenticated_client as client:
|
|
33
32
|
machine = await client.compute(test_machine)
|
|
34
33
|
await machine.jobs(user=test_username)
|
|
35
34
|
|
|
36
35
|
|
|
37
36
|
@pytest.mark.asyncio
|
|
38
|
-
async def test_list_dir_contents(
|
|
39
|
-
|
|
37
|
+
async def test_list_dir_contents(
|
|
38
|
+
async_authenticated_client, test_machine, test_job_path
|
|
39
|
+
):
|
|
40
|
+
async with async_authenticated_client as client:
|
|
40
41
|
machine = await client.compute(test_machine)
|
|
41
42
|
test_job = Path(test_job_path)
|
|
42
43
|
test_path = test_job.parent
|
|
@@ -54,8 +55,8 @@ async def test_list_dir_contents(client_id, client_secret, test_machine, test_jo
|
|
|
54
55
|
|
|
55
56
|
|
|
56
57
|
@pytest.mark.asyncio
|
|
57
|
-
async def test_list_dir(
|
|
58
|
-
async with
|
|
58
|
+
async def test_list_dir(async_authenticated_client, test_machine, test_job_path):
|
|
59
|
+
async with async_authenticated_client as client:
|
|
59
60
|
machine = await client.compute(test_machine)
|
|
60
61
|
test_job = Path(test_job_path)
|
|
61
62
|
test_path = test_job.parent
|
|
@@ -66,8 +67,8 @@ async def test_list_dir(client_id, client_secret, test_machine, test_job_path):
|
|
|
66
67
|
|
|
67
68
|
|
|
68
69
|
@pytest.mark.asyncio
|
|
69
|
-
async def test_list_file(
|
|
70
|
-
async with
|
|
70
|
+
async def test_list_file(async_authenticated_client, test_machine, test_job_path):
|
|
71
|
+
async with async_authenticated_client as client:
|
|
71
72
|
machine = await client.compute(test_machine)
|
|
72
73
|
test_job_filename = Path(test_job_path).name
|
|
73
74
|
test_job_dir = Path(test_job_path).parent
|
|
@@ -84,8 +85,8 @@ async def test_list_file(client_id, client_secret, test_machine, test_job_path):
|
|
|
84
85
|
|
|
85
86
|
|
|
86
87
|
@pytest.mark.asyncio
|
|
87
|
-
async def test_run_arg_list(
|
|
88
|
-
async with
|
|
88
|
+
async def test_run_arg_list(async_authenticated_client, test_machine, test_job_path):
|
|
89
|
+
async with async_authenticated_client as client:
|
|
89
90
|
machine = await client.compute(test_machine)
|
|
90
91
|
output = await machine.run(["ls", test_job_path])
|
|
91
92
|
|
|
@@ -93,8 +94,8 @@ async def test_run_arg_list(client_id, client_secret, test_machine, test_job_pat
|
|
|
93
94
|
|
|
94
95
|
|
|
95
96
|
@pytest.mark.asyncio
|
|
96
|
-
async def test_run_arg_str(
|
|
97
|
-
async with
|
|
97
|
+
async def test_run_arg_str(async_authenticated_client, test_machine, test_job_path):
|
|
98
|
+
async with async_authenticated_client as client:
|
|
98
99
|
machine = await client.compute(test_machine)
|
|
99
100
|
output = await machine.run(f"ls {test_job_path}")
|
|
100
101
|
|
|
@@ -102,8 +103,8 @@ async def test_run_arg_str(client_id, client_secret, test_machine, test_job_path
|
|
|
102
103
|
|
|
103
104
|
|
|
104
105
|
@pytest.mark.asyncio
|
|
105
|
-
async def test_run_arg_path(
|
|
106
|
-
async with
|
|
106
|
+
async def test_run_arg_path(async_authenticated_client, test_machine):
|
|
107
|
+
async with async_authenticated_client as client:
|
|
107
108
|
machine = await client.compute(test_machine)
|
|
108
109
|
[remote_path] = await machine.ls("/usr/bin/hostname")
|
|
109
110
|
output = await machine.run(remote_path)
|
|
@@ -112,8 +113,8 @@ async def test_run_arg_path(client_id, client_secret, test_machine):
|
|
|
112
113
|
|
|
113
114
|
|
|
114
115
|
@pytest.mark.asyncio
|
|
115
|
-
async def test_outages(
|
|
116
|
-
async with
|
|
116
|
+
async def test_outages(async_authenticated_client, test_machine):
|
|
117
|
+
async with async_authenticated_client as client:
|
|
117
118
|
machine = await client.compute(test_machine)
|
|
118
119
|
outages = await machine.outages()
|
|
119
120
|
|
|
@@ -121,8 +122,8 @@ async def test_outages(client_id, client_secret, test_machine):
|
|
|
121
122
|
|
|
122
123
|
|
|
123
124
|
@pytest.mark.asyncio
|
|
124
|
-
async def test_planned_outages(
|
|
125
|
-
async with
|
|
125
|
+
async def test_planned_outages(async_authenticated_client, test_machine):
|
|
126
|
+
async with async_authenticated_client as client:
|
|
126
127
|
machine = await client.compute(test_machine)
|
|
127
128
|
outages = await machine.planned_outages()
|
|
128
129
|
|