Skip to content

Commit a86ff1b

Browse files
committed
Add projects endpoint
1 parent 1b916c8 commit a86ff1b

6 files changed

Lines changed: 439 additions & 34 deletions

File tree

metafold/__init__.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,22 @@
11
from metafold.client import Client
2+
from metafold.projects import ProjectsEndpoint
23
from metafold.assets import AssetsEndpoint
34
from metafold.jobs import JobsEndpoint
5+
from typing import Optional
46

57

68
class MetafoldClient(Client):
79
"""Metafold REST API client.
810
911
Attributes:
12+
projects: Sub-client for projects endpoint.
1013
assets: Sub-client for assets endpoint.
1114
jobs: Sub-client for jobs endpoint.
1215
"""
1316

1417
def __init__(
15-
self, access_token: str, project_id: str,
18+
self, access_token: str,
19+
project_id: Optional[str] = None,
1620
base_url: str = "https://api.metafold3d.com",
1721
) -> None:
1822
"""Initialize Metafold API client.
@@ -22,6 +26,7 @@ def __init__(
2226
project_id: ID of the project to make API calls against.
2327
base_url: Metafold API URL. Used for internal testing.
2428
"""
25-
super().__init__(access_token, project_id, base_url=base_url)
29+
super().__init__(access_token, base_url, project_id=project_id)
30+
self.projects = ProjectsEndpoint(self)
2631
self.assets = AssetsEndpoint(self)
2732
self.jobs = JobsEndpoint(self)

metafold/assets.py

Lines changed: 43 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,12 @@ class AssetsEndpoint:
3434
def __init__(self, client: Client) -> None:
3535
self._client = client
3636

37-
def list(self, sort: Optional[str] = None, q: Optional[str] = None) -> list[Asset]:
37+
def list(
38+
self,
39+
sort: Optional[str] = None,
40+
q: Optional[str] = None,
41+
project_id: Optional[str] = None,
42+
) -> list[Asset]:
3843
"""List assets.
3944
4045
Args:
@@ -43,84 +48,106 @@ def list(self, sort: Optional[str] = None, q: Optional[str] = None) -> list[Asse
4348
"modified".
4449
q: Query string. For details on syntax see the Metafold API docs.
4550
Supported search fields are: "id" and "filename".
51+
project_id: Asset project ID.
4652
4753
Returns:
4854
List of asset resources.
4955
"""
50-
url = f"/projects/{self._client.project_id}/assets"
56+
project_id = self._client.project_id(project_id)
57+
url = f"/projects/{project_id}/assets"
5158
payload = asdict(sort=sort, q=q)
5259
r: Response = self._client.get(url, params=payload)
5360
return [Asset(**a) for a in r.json()]
5461

55-
def get(self, id: str) -> Asset:
62+
def get(self, asset_id: str, project_id: Optional[str] = None) -> Asset:
5663
"""Get an asset.
5764
5865
Args:
59-
id: ID of asset to get.
66+
asset_id: ID of asset to get.
67+
project_id: Asset project ID.
6068
6169
Returns:
6270
Asset resource.
6371
"""
64-
url = f"/projects/{self._client.project_id}/assets/{id}"
72+
project_id = self._client.project_id(project_id)
73+
url = f"/projects/{project_id}/assets/{asset_id}"
6574
r: Response = self._client.get(url)
6675
return Asset(**r.json())
6776

68-
def download_file(self, id: str, path: Union[str, PathLike]):
77+
def download_file(
78+
self, asset_id: str, path: Union[str, PathLike],
79+
project_id: Optional[str] = None,
80+
):
6981
"""Download an asset.
7082
7183
Args:
72-
id: ID of asset to download.
84+
asset_id: ID of asset to download.
7385
path: Path to downloaded file.
86+
project_id: Asset project ID.
7487
"""
75-
url = f"/projects/{self._client.project_id}/assets/{id}"
88+
project_id = self._client.project_id(project_id)
89+
url = f"/projects/{project_id}/assets/{asset_id}"
7690
r: Response = self._client.get(url, params={"download": "true"})
7791
r = requests.get(r.json()["link"], stream=True)
7892
with open(path, "wb") as f:
7993
for chunk in r.iter_content(chunk_size=65536): # 64 KiB
8094
f.write(chunk)
8195

82-
def create(self, f: Union[str, bytes, PathLike, IO[bytes]]) -> Asset:
96+
def create(
97+
self, f: Union[str, bytes, PathLike, IO[bytes]],
98+
project_id: Optional[str] = None,
99+
) -> Asset:
83100
"""Upload an asset.
84101
85102
Args:
86103
f: File-like object (opened in binary mode) or path to file on disk.
104+
project_id: Asset project ID.
87105
88106
Returns:
89107
Asset resource.
90108
"""
109+
project_id = self._client.project_id(project_id)
91110
fp: IO[bytes] = _open_file(f)
92111
try:
93-
url = f"/projects/{self._client.project_id}/assets"
112+
url = f"/projects/{project_id}/assets"
94113
r: Response = self._client.post(url, files={"file": fp})
95114
finally:
96115
fp.close()
97116
return Asset(**r.json())
98117

99-
def update(self, id: str, f: Union[str, bytes, PathLike, IO[bytes]]) -> Asset:
118+
def update(
119+
self, asset_id: str,
120+
f: Union[str, bytes, PathLike, IO[bytes]],
121+
project_id: Optional[str] = None,
122+
) -> Asset:
100123
"""Update an asset.
101124
102125
Args:
103-
id: ID of asset to update.
126+
asset_id: ID of asset to update.
104127
f: File-like object (opened in binary mode) or path to file on disk.
128+
project_id: Asset project ID.
105129
106130
Returns:
107131
Updated asset resource.
108132
"""
133+
project_id = self._client.project_id(project_id)
109134
fp: IO[bytes] = _open_file(f)
110135
try:
111-
url = f"/projects/{self._client.project_id}/assets/{id}"
136+
url = f"/projects/{project_id}/assets/{asset_id}"
112137
r: Response = self._client.patch(url, files={"file": fp})
113138
finally:
114139
fp.close()
115140
return Asset(**r.json())
116141

117-
def delete(self, id: str) -> None:
142+
def delete(self, asset_id: str, project_id: Optional[str] = None) -> None:
118143
"""Delete an asset.
119144
120145
Args:
121-
id: ID of asset to delete.
146+
asset_id: ID of asset to delete.
147+
project_id: Asset project ID.
122148
"""
123-
url = f"/projects/{self._client.project_id}/assets/{id}"
149+
project_id = self._client.project_id(project_id)
150+
url = f"/projects/{project_id}/assets/{asset_id}"
124151
self._client.delete(url)
125152

126153

metafold/client.py

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,17 @@
11
from requests import HTTPError, Response, Session
2-
from typing import Any, Callable
2+
from typing import Any, Callable, Optional
33
from urllib.parse import urljoin
44
import platform
55

66

77
class Client:
88
"""Base client."""
99

10-
def __init__(self, access_token: str, project_id: str, base_url: str) -> None:
11-
# TODO(ryan): Validate project id
12-
self._project_id = project_id
10+
def __init__(
11+
self, access_token: str, base_url: str,
12+
project_id: Optional[str] = None,
13+
) -> None:
14+
self._default_project = project_id
1315
self._base_url = base_url
1416
self._session = Session()
1517
self._session.headers.update({
@@ -18,9 +20,13 @@ def __init__(self, access_token: str, project_id: str, base_url: str) -> None:
1820
"User-Agent": f"Python/{platform.python_version()}",
1921
})
2022

21-
@property
22-
def project_id(self) -> str:
23-
return self._project_id
23+
def project_id(self, id: Optional[str] = None) -> str:
24+
id = id or self._default_project
25+
if not id:
26+
raise ValueError(
27+
"Project ID required, set a default ID when initializing the client"
28+
)
29+
return id
2430

2531
def _request(
2632
self, request: Callable[..., Response], url: str,

metafold/jobs.py

Lines changed: 27 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -43,40 +43,50 @@ class JobsEndpoint:
4343
def __init__(self, client: Client) -> None:
4444
self._client = client
4545

46-
def list(self, sort: Optional[str] = None, q: Optional[str] = None) -> list[Job]:
46+
def list(
47+
self,
48+
sort: Optional[str] = None,
49+
q: Optional[str] = None,
50+
project_id: Optional[str] = None,
51+
) -> list[Job]:
4752
"""List jobs.
4853
4954
Args:
5055
sort: Sort string. For details on syntax see the Metafold API docs.
5156
Supported sorting fields are: "id", "name", or "created".
5257
q: Query string. For details on syntax see the Metafold API docs.
5358
Supported search fields are: "id", "name", "type", and "state".
59+
project_id: Job project ID.
5460
5561
Returns:
5662
List of job resources.
5763
"""
58-
url = f"/projects/{self._client.project_id}/jobs"
64+
project_id = self._client.project_id(project_id)
65+
url = f"/projects/{project_id}/jobs"
5966
payload = asdict(sort=sort, q=q)
6067
r: Response = self._client.get(url, params=payload)
6168
return [Job(**j) for j in r.json()]
6269

63-
def get(self, id: str) -> Job:
70+
def get(self, job_id: str, project_id: Optional[str] = None) -> Job:
6471
"""Get a job.
6572
6673
Args:
67-
id: ID of job to get.
74+
job_id: ID of job to get.
75+
project_id: Job project ID.
6876
6977
Returns:
7078
Job resource.
7179
"""
72-
url = f"/projects/{self._client.project_id}/jobs/{id}"
80+
project_id = self._client.project_id(project_id)
81+
url = f"/projects/{project_id}/jobs/{job_id}"
7382
r: Response = self._client.get(url)
7483
return Job(**r.json())
7584

7685
def run(
7786
self, type: str, params: dict[str, Any],
7887
name: Optional[str] = None,
7988
timeout: Union[int, float] = 120,
89+
project_id: Optional[str] = None,
8090
) -> Job:
8191
"""Dispatch a new job and wait for a result.
8292
@@ -87,26 +97,34 @@ def run(
8797
params: Job parameters.
8898
name: Optional job name.
8999
timeout: Time in seconds to wait for a result.
100+
project_id: Job project ID.
90101
91102
Returns:
92103
Completed job resource.
93104
"""
105+
project_id = self._client.project_id(project_id)
94106
payload = asdict(type=type, parameters=params, name=name)
95-
r: Response = self._client.post(f"/projects/{self._client.project_id}/jobs", json=payload)
107+
r: Response = self._client.post(f"/projects/{project_id}/jobs", json=payload)
96108
r = self._poll(r.json()["link"], timeout)
97109
return Job(**r.json())
98110

99-
def update(self, id: str, name: Optional[str] = None) -> Job:
111+
def update(
112+
self, job_id: str,
113+
name: Optional[str] = None,
114+
project_id: Optional[str] = None,
115+
) -> Job:
100116
"""Update a job.
101117
102118
Args:
103-
id: ID of job to update.
119+
job_id: ID of job to update.
104120
name: New job name. The existing name remains unchanged if None.
121+
project_id: Job project ID.
105122
106123
Returns:
107124
Updated job resource.
108125
"""
109-
url = f"/projects/{self._client.project_id}/jobs/{id}"
126+
project_id = self._client.project_id(project_id)
127+
url = f"/projects/{project_id}/jobs/{job_id}"
110128
payload = asdict(name=name)
111129
r: Response = self._client.patch(url, data=payload)
112130
return Job(**r.json())

0 commit comments

Comments
 (0)