Skip to content

Commit 5582da5

Browse files
committed
Merge branch 'auth' into 'master'
Auth See merge request metafold/metafold-python!1
2 parents 0747198 + 91e2389 commit 5582da5

4 files changed

Lines changed: 73 additions & 5 deletions

File tree

metafold/__init__.py

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
from metafold.projects import ProjectsEndpoint
33
from metafold.assets import AssetsEndpoint
44
from metafold.jobs import JobsEndpoint
5+
from metafold.auth import AuthProvider
56
from typing import Optional
67

78

@@ -18,8 +19,12 @@ class MetafoldClient(Client):
1819
jobs: JobsEndpoint
1920

2021
def __init__(
21-
self, access_token: str,
22+
self,
23+
access_token: Optional[str] = None,
2224
project_id: Optional[str] = None,
25+
client_id: Optional[str] = None,
26+
client_secret: Optional[str] = None,
27+
auth_domain: str = "metafold3d.us.auth0.com",
2328
base_url: str = "https://api.metafold3d.com",
2429
) -> None:
2530
"""Initialize Metafold API client.
@@ -29,7 +34,17 @@ def __init__(
2934
project_id: ID of the project to make API calls against.
3035
base_url: Metafold API URL. Used for internal testing.
3136
"""
32-
super().__init__(access_token, base_url, project_id=project_id)
37+
# client_id and client_secret have priority
38+
if not any([client_id and client_secret, access_token]):
39+
raise ValueError(
40+
"Expected client_id and client_secret or access_token to be provided"
41+
)
42+
elif client_id and client_secret:
43+
auth = AuthProvider(client_id, client_secret, auth_domain, base_url)
44+
super().__init__(base_url, auth=auth, project_id=project_id)
45+
else:
46+
super().__init__(base_url, access_token=access_token, project_id=project_id)
47+
3348
self.projects = ProjectsEndpoint(self)
3449
self.assets = AssetsEndpoint(self)
3550
self.jobs = JobsEndpoint(self)

metafold/auth.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
from auth0.authentication import GetToken
2+
from collections import namedtuple
3+
from datetime import datetime, timedelta, timezone
4+
from typing import Optional
5+
6+
Token = namedtuple("Token", ["access_token", "expires_at"])
7+
8+
9+
class AuthProvider:
10+
def __init__(
11+
self,
12+
client_id: str,
13+
client_secret: str,
14+
auth_domain: str,
15+
base_url: str
16+
) -> None:
17+
self._auth_domain = auth_domain
18+
self._base_url = base_url
19+
self._client_id = client_id
20+
21+
self._get_token = GetToken(
22+
self._auth_domain,
23+
self._client_id,
24+
client_secret=client_secret,
25+
)
26+
self._token: Optional[Token] = None
27+
28+
def get_token(self) -> str:
29+
now = datetime.now(timezone.utc)
30+
if not self._token or self._token.expires_at - now < timedelta(minutes=1):
31+
token = self._get_token.client_credentials(self._base_url)
32+
expires_at = now + timedelta(seconds=token["expires_in"])
33+
self._token = Token(token["access_token"], expires_at)
34+
return self._token.access_token

metafold/client.py

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
from metafold.auth import AuthProvider
12
from requests import HTTPError, Response, Session
23
from typing import Any, Callable, Optional
34
from urllib.parse import urljoin
@@ -8,17 +9,28 @@ class Client:
89
"""Base client."""
910

1011
def __init__(
11-
self, access_token: str, base_url: str,
12+
self,
13+
base_url: str,
14+
access_token: Optional[str] = None,
1215
project_id: Optional[str] = None,
16+
auth: Optional[AuthProvider] = None
1317
) -> None:
18+
if bool(auth) == bool(access_token):
19+
raise ValueError(
20+
"Expected AuthProvider or access_token to be provided"
21+
)
22+
self._auth = auth
1423
self._default_project = project_id
1524
self._base_url = base_url
1625
self._session = Session()
1726
self._session.headers.update({
1827
"Accept": "application/json",
19-
"Authorization": f"Bearer {access_token}",
2028
"User-Agent": f"Python/{platform.python_version()}",
2129
})
30+
if access_token:
31+
self._session.headers.update({
32+
"Authorization": f"Bearer {access_token}",
33+
})
2234

2335
def project_id(self, id: Optional[str] = None) -> str:
2436
id = id or self._default_project
@@ -28,12 +40,18 @@ def project_id(self, id: Optional[str] = None) -> str:
2840
)
2941
return id
3042

43+
def set_project_id(self, id: str) -> None:
44+
self._default_project = id
45+
3146
def _request(
3247
self, request: Callable[..., Response], url: str,
3348
*args: Any, **kwargs: Any,
3449
) -> Response:
3550
url = urljoin(self._base_url, url)
36-
r: Response = request(url, *args, **kwargs)
51+
headers = None
52+
if self._auth:
53+
headers = {"Authorization": f"Bearer {self._auth.get_token()}"}
54+
r: Response = request(url, *args, **kwargs, headers=headers)
3755
if not r.ok:
3856
body: dict[str, Any] = r.json()
3957
# Error responses aren't entirely consistent in the Metafold API,

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ authors = [
1010
]
1111
dependencies = [
1212
"attrs~=23.2",
13+
"auth0-python~=4.7",
1314
"requests~=2.31",
1415
]
1516
readme = "README.md"

0 commit comments

Comments
 (0)