Skip to content

Commit 857aa76

Browse files
committed
added logic to create oauth client
1 parent fe8cda9 commit 857aa76

6 files changed

Lines changed: 275 additions & 6 deletions

File tree

pyatlan/client/aio/oauth_client.py

Lines changed: 57 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,20 +3,25 @@
33

44
from __future__ import annotations
55

6-
from typing import Optional
6+
from typing import TYPE_CHECKING, List, Optional
77

88
from pydantic.v1 import validate_arguments
99

1010
from pyatlan.client.common import (
1111
AsyncApiCaller,
12+
OAuthClientCreate,
1213
OAuthClientGet,
1314
OAuthClientGetAll,
1415
OAuthClientGetById,
1516
OAuthClientPurge,
1617
OAuthClientUpdate,
18+
RoleGet,
1719
)
1820
from pyatlan.errors import ErrorCode
19-
from pyatlan.model.oauth_clients import OAuthClient, OAuthClientResponse
21+
from pyatlan.model.oauth_clients import OAuthClient, OAuthClientCreateResponse, OAuthClientResponse
22+
23+
if TYPE_CHECKING:
24+
from pyatlan.client.aio import AsyncAtlanClient
2025

2126

2227
class AsyncOAuthClientClient:
@@ -107,3 +112,53 @@ async def purge(self, client_id: str) -> None:
107112
"""
108113
endpoint, _ = OAuthClientPurge.prepare_request(client_id)
109114
await self._client._call_api(endpoint)
115+
116+
async def _fetch_available_roles(self):
117+
"""
118+
Fetch all available roles (workspace and admin-subrole levels).
119+
120+
:returns: list of AtlanRole objects
121+
"""
122+
filter_str = OAuthClientCreate.build_roles_filter()
123+
endpoint, query_params = RoleGet.prepare_request(
124+
limit=100,
125+
post_filter=filter_str,
126+
)
127+
raw_json = await self._client._call_api(endpoint, query_params)
128+
response = RoleGet.process_response(raw_json)
129+
return response.records or []
130+
131+
@validate_arguments
132+
async def create(
133+
self,
134+
name: str,
135+
role: str,
136+
description: Optional[str] = None,
137+
persona_qns: Optional[List[str]] = None,
138+
) -> OAuthClientCreateResponse:
139+
"""
140+
Create a new OAuth client with the provided settings.
141+
142+
:param name: human-readable name for the OAuth client (displayed in UI)
143+
:param role: role description to assign to the OAuth client (e.g., 'Admin', 'Member',
144+
'Guest', 'Admins (Connections)'). This is matched against available role
145+
descriptions and the corresponding role name is used in the API payload.
146+
:param description: optional explanation of the OAuth client
147+
:param persona_qns: qualified names of personas to associate with the OAuth client
148+
:returns: the created OAuthClientCreateResponse (includes client_id and client_secret)
149+
:raises AtlanError: on any API communication issue
150+
:raises ValueError: if the specified role description is not found
151+
"""
152+
# Fetch available roles and resolve the user-provided role name
153+
available_roles = await self._fetch_available_roles()
154+
resolved_role = OAuthClientCreate.resolve_role_name(role, available_roles)
155+
156+
# Prepare and execute the request
157+
endpoint, request_obj = OAuthClientCreate.prepare_request(
158+
display_name=name,
159+
role=resolved_role,
160+
description=description,
161+
persona_qns=persona_qns,
162+
)
163+
raw_json = await self._client._call_api(endpoint, request_obj=request_obj)
164+
return OAuthClientCreate.process_response(raw_json)

pyatlan/client/common/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,7 @@
9999

100100
# OAuth client shared logic classes
101101
from .oauth_client import (
102+
OAuthClientCreate,
102103
OAuthClientGet,
103104
OAuthClientGetAll,
104105
OAuthClientGetById,
@@ -265,6 +266,7 @@
265266
"ImpersonateGetUserId",
266267
"ImpersonateUser",
267268
# OAuth client shared logic classes
269+
"OAuthClientCreate",
268270
"OAuthClientGet",
269271
"OAuthClientGetAll",
270272
"OAuthClientGetById",

pyatlan/client/common/oauth_client.py

Lines changed: 97 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,22 @@
33

44
from __future__ import annotations
55

6-
from typing import Dict, Optional
6+
from typing import Dict, List, Optional
77

88
from pyatlan.client.constants import (
9+
CREATE_OAUTH_CLIENT,
910
DELETE_OAUTH_CLIENT,
1011
GET_OAUTH_CLIENT_BY_ID,
1112
GET_OAUTH_CLIENTS,
1213
UPDATE_OAUTH_CLIENT,
1314
)
14-
from pyatlan.model.oauth_clients import OAuthClient, OAuthClientResponse
15+
from pyatlan.model.oauth_clients import (
16+
OAuthClient,
17+
OAuthClientCreateResponse,
18+
OAuthClientRequest,
19+
OAuthClientResponse,
20+
)
21+
from pyatlan.model.role import AtlanRole
1522

1623

1724
class OAuthClientGetAll:
@@ -158,3 +165,91 @@ def prepare_request(client_id: str) -> tuple:
158165
{"client_id": client_id}
159166
).format_path_with_params()
160167
return endpoint, None
168+
169+
170+
class OAuthClientCreate:
171+
"""Shared logic for creating OAuth clients."""
172+
173+
@staticmethod
174+
def resolve_role_name(role: str, available_roles: List[AtlanRole]) -> str:
175+
"""
176+
Resolve the user-provided role to the actual API role name.
177+
178+
The user provides a role description (e.g., 'Admin', 'Member', 'Admins (Connections)')
179+
and we find the corresponding role name (e.g., '$admin', '$member', '$admin_connections')
180+
to send in the API payload.
181+
182+
:param role: user-provided role description
183+
:param available_roles: list of available roles from the API
184+
:returns: the actual API role name (e.g., '$admin')
185+
:raises ValueError: if the role description is not found
186+
"""
187+
role_lower = role.lower().strip()
188+
189+
# Build lookup: lowercased description -> role name
190+
desc_to_name: Dict[str, str] = {}
191+
available_descriptions: List[str] = []
192+
193+
for r in available_roles:
194+
if r.description and r.name:
195+
desc_to_name[r.description.lower()] = r.name
196+
available_descriptions.append(r.description)
197+
198+
# Match against description
199+
if role_lower in desc_to_name:
200+
return desc_to_name[role_lower]
201+
202+
# No match found - raise error with available descriptions
203+
raise ValueError(
204+
f"Role '{role}' not found. Available roles: {', '.join(sorted(available_descriptions))}"
205+
)
206+
207+
@staticmethod
208+
def build_roles_filter() -> str:
209+
"""
210+
Build the filter string to fetch workspace and admin-subrole level roles.
211+
212+
:returns: JSON filter string
213+
"""
214+
import json
215+
216+
return json.dumps(
217+
{"$or": [{"level": "workspace"}, {"level": "admin-subrole"}]}
218+
)
219+
220+
@staticmethod
221+
def prepare_request(
222+
display_name: str,
223+
role: str,
224+
description: Optional[str] = None,
225+
persona_qns: Optional[List[str]] = None,
226+
) -> tuple:
227+
"""
228+
Prepare the request for creating an OAuth client.
229+
230+
Note: The role should already be resolved to the actual API value
231+
(e.g., '$admin') before calling this method.
232+
233+
:param display_name: human-readable name for the OAuth client
234+
:param role: role assigned to the OAuth client (must be the actual API value like '$admin')
235+
:param description: optional explanation of the OAuth client
236+
:param persona_qns: qualified names of personas to associate with the OAuth client
237+
:returns: tuple of (endpoint, request_dict)
238+
"""
239+
request = OAuthClientRequest(
240+
display_name=display_name,
241+
description=description,
242+
role=role,
243+
persona_qns=persona_qns,
244+
)
245+
return CREATE_OAUTH_CLIENT.format_path_with_params(), request
246+
247+
@staticmethod
248+
def process_response(raw_json: Dict) -> OAuthClientCreateResponse:
249+
"""
250+
Process the API response into an OAuthClientCreateResponse.
251+
252+
:param raw_json: raw response from the API
253+
:returns: the created OAuthClientCreateResponse (includes client_secret)
254+
"""
255+
return OAuthClientCreateResponse(**raw_json)

pyatlan/client/constants.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,9 @@
102102
GET_OAUTH_CLIENTS = API(
103103
OAUTH_CLIENTS_API, HTTPMethod.GET, HTTPStatus.OK, endpoint=EndPoint.HERACLES
104104
)
105+
CREATE_OAUTH_CLIENT = API(
106+
OAUTH_CLIENTS_API, HTTPMethod.POST, HTTPStatus.OK, endpoint=EndPoint.HERACLES
107+
)
105108
GET_OAUTH_CLIENT_BY_ID = API(
106109
OAUTH_CLIENTS_API + "/{client_id}",
107110
HTTPMethod.GET,

pyatlan/client/oauth_client.py

Lines changed: 55 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,20 +2,25 @@
22
# Copyright 2025 Atlan Pte. Ltd.
33
from __future__ import annotations
44

5-
from typing import Optional
5+
from typing import TYPE_CHECKING, List, Optional
66

77
from pydantic.v1 import validate_arguments
88

99
from pyatlan.client.common import (
1010
ApiCaller,
11+
OAuthClientCreate,
1112
OAuthClientGet,
1213
OAuthClientGetAll,
1314
OAuthClientGetById,
1415
OAuthClientPurge,
1516
OAuthClientUpdate,
17+
RoleGet,
1618
)
1719
from pyatlan.errors import ErrorCode
18-
from pyatlan.model.oauth_clients import OAuthClient, OAuthClientResponse
20+
from pyatlan.model.oauth_clients import OAuthClient, OAuthClientCreateResponse, OAuthClientResponse
21+
22+
if TYPE_CHECKING:
23+
from pyatlan.client.atlan import AtlanClient
1924

2025

2126
class OAuthClientClient:
@@ -108,3 +113,51 @@ def purge(self, client_id: str) -> None:
108113
"""
109114
endpoint, _ = OAuthClientPurge.prepare_request(client_id)
110115
self._client._call_api(endpoint)
116+
117+
def _fetch_available_roles(self):
118+
"""
119+
Fetch all available roles (workspace and admin-subrole levels).
120+
121+
:returns: list of AtlanRole objects
122+
"""
123+
filter_str = OAuthClientCreate.build_roles_filter()
124+
endpoint, query_params = RoleGet.prepare_request(
125+
limit=100,
126+
post_filter=filter_str,
127+
)
128+
raw_json = self._client._call_api(endpoint, query_params)
129+
response = RoleGet.process_response(raw_json)
130+
return response.records or []
131+
132+
@validate_arguments
133+
def create(
134+
self,
135+
name: str,
136+
role: str,
137+
description: Optional[str] = None,
138+
persona_qns: Optional[List[str]] = None,
139+
) -> OAuthClientCreateResponse:
140+
"""
141+
Create a new OAuth client with the provided settings.
142+
143+
:param name: human-readable name for the OAuth client (displayed in UI)
144+
:param role: role description to assign to the OAuth client (e.g., 'Admin', 'Member').
145+
:param description: optional explanation of the OAuth client
146+
:param persona_qns: qualified names of personas to associate with the OAuth client
147+
:returns: the created OAuthClientCreateResponse (includes client_id and client_secret)
148+
:raises AtlanError: on any API communication issue
149+
:raises ValueError: if the specified role description is not found
150+
"""
151+
# Fetch available roles and resolve the user-provided role name
152+
available_roles = self._fetch_available_roles()
153+
resolved_role = OAuthClientCreate.resolve_role_name(role, available_roles)
154+
155+
# Prepare and execute the request
156+
endpoint, request_obj = OAuthClientCreate.prepare_request(
157+
display_name=name,
158+
role=resolved_role,
159+
description=description,
160+
persona_qns=persona_qns,
161+
)
162+
raw_json = self._client._call_api(endpoint, request_obj=request_obj)
163+
return OAuthClientCreate.process_response(raw_json)

pyatlan/model/oauth_clients.py

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,67 @@
66

77
from pyatlan.model.core import AtlanObject
88

9+
class OAuthClientRequest(AtlanObject):
10+
"""Request object for creating an OAuth client."""
11+
12+
display_name: str = Field(
13+
description="Human-readable name for the OAuth client.",
14+
)
15+
description: Optional[str] = Field(
16+
default=None,
17+
description="Explanation of the OAuth client.",
18+
)
19+
role: str = Field(
20+
description="Role assigned to the OAuth client (e.g., '$admin', '$member').",
21+
)
22+
persona_qns: Optional[List[str]] = Field(
23+
default=None,
24+
description="Qualified names of personas to associate with the OAuth client.",
25+
)
26+
27+
28+
class OAuthClientCreateResponse(AtlanObject):
29+
"""Response object returned when creating an OAuth client (includes client secret)."""
30+
31+
id: Optional[str] = Field(
32+
default=None,
33+
description="Unique identifier (GUID) of the OAuth client.",
34+
)
35+
client_id: Optional[str] = Field(
36+
default=None,
37+
description="Unique client identifier of the OAuth client.",
38+
alias="clientId",
39+
)
40+
client_secret: Optional[str] = Field(
41+
default=None,
42+
description="Client secret for the OAuth client (only returned on creation).",
43+
alias="clientSecret",
44+
)
45+
display_name: Optional[str] = Field(
46+
default=None,
47+
description="Human-readable name provided when creating the OAuth client.",
48+
alias="displayName",
49+
)
50+
description: Optional[str] = Field(
51+
default=None,
52+
description="Explanation of the OAuth client.",
53+
)
54+
token_expiry_seconds: Optional[int] = Field(
55+
default=None,
56+
description="Time in seconds after which the token will expire.",
57+
alias="tokenExpirySeconds",
58+
)
59+
created_at: Optional[str] = Field(
60+
default=None,
61+
description="Epoch time, in milliseconds, at which the OAuth client was created.",
62+
alias="createdAt",
63+
)
64+
created_by: Optional[str] = Field(
65+
default=None,
66+
description="User who created the OAuth client.",
67+
alias="createdBy",
68+
)
69+
970

1071
class OAuthClient(AtlanObject):
1172
"""Represents an OAuth client credential in Atlan."""

0 commit comments

Comments
 (0)