adaptive-sdk 0.1.14__py3-none-any.whl → 0.12.1__py3-none-any.whl

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.
@@ -1,27 +1,27 @@
1
1
  from __future__ import annotations
2
- import os
2
+
3
3
  import io
4
- import zipfile
5
4
  import mimetypes
5
+ import os
6
+ import zipfile
6
7
  from contextlib import contextmanager
7
- from loguru import logger
8
- from hypothesis_jsonschema import from_schema
9
- from typing import TYPE_CHECKING, Sequence, Any
10
8
  from pathlib import Path
9
+ from typing import TYPE_CHECKING, Any, Sequence
11
10
 
12
- from adaptive_sdk.graphql_client.fragments import JobData
13
- from adaptive_sdk.graphql_client.input_types import JobInput
11
+ from hypothesis_jsonschema import from_schema
12
+ from loguru import logger
14
13
 
15
- from .base_resource import SyncAPIResource, AsyncAPIResource, UseCaseResource
16
14
  from adaptive_sdk.graphql_client import (
15
+ CreateRecipeInput,
17
16
  CustomRecipeData,
18
17
  CustomRecipeFilterInput,
19
- CreateRecipeInput,
20
- UpdateRecipeInput,
21
18
  LabelInput,
19
+ UpdateRecipeInput,
22
20
  Upload,
23
21
  )
24
22
 
23
+ from .base_resource import AsyncAPIResource, SyncAPIResource, UseCaseResource
24
+
25
25
  if TYPE_CHECKING:
26
26
  from adaptive_sdk.client import Adaptive, AsyncAdaptive
27
27
 
@@ -36,6 +36,14 @@ class Recipes(SyncAPIResource, UseCaseResource): # type: ignore[misc]
36
36
  UseCaseResource.__init__(self, client)
37
37
 
38
38
  def list(self, use_case: str | None = None) -> Sequence[CustomRecipeData]:
39
+ """List all custom recipes for a use case.
40
+
41
+ Args:
42
+ use_case: Optional use case key. Falls back to client's default.
43
+
44
+ Returns:
45
+ A sequence of custom recipe data objects.
46
+ """
39
47
  filter = CustomRecipeFilterInput()
40
48
  return self._gql_client.list_custom_recipes(use_case=self.use_case_key(use_case), filter=filter).custom_recipes
41
49
 
@@ -53,18 +61,27 @@ class Recipes(SyncAPIResource, UseCaseResource): # type: ignore[misc]
53
61
  Upload a recipe from either a single Python file or a directory (path).
54
62
 
55
63
  Args:
56
- path: Path to a Python file or directory containing the recipe
64
+ path: Path to a Python file or directory containing the recipe.
57
65
  recipe_key: Optional unique key for the recipe. If not provided, inferred from:
58
66
  - File name (without .py) if path is a file
59
67
  - "dir_name/entrypoint_name" if path is a directory and custom entrypoint is specified
60
68
  - Directory name if path is a directory and no custom entrypoint is specified
61
- entrypoint: Optional relative path to the entrypoint file within a directory.
62
- If specified, this file will be used as main.py. Cannot be used if
63
- main.py already exists in the directory, or if path is a file.
64
- name: Optional display name for the recipe
65
- description: Optional description
66
- labels: Optional key-value labels
67
- use_case: Optional use case identifier
69
+ entrypoint: Optional path to the recipe entrypoint file, relative to the `path` directory.
70
+ **Only applicable when path is a directory.**
71
+
72
+ Raises ValueError if:
73
+ - path is a single file (entrypoint not supported for single files)
74
+ - path is a directory that already contains main.py
75
+
76
+ Raises FileNotFoundError if:
77
+ - the specified entrypoint file doesn't exist in the directory
78
+
79
+ If path is a directory and entrypoint is None:
80
+ - The directory must contain a main.py file, or FileNotFoundError is raised
81
+ name: Optional display name for the recipe.
82
+ description: Optional description.
83
+ labels: Optional key-value labels.
84
+ use_case: Optional use case identifier.
68
85
  """
69
86
  p = Path(path)
70
87
  if recipe_key is None:
@@ -91,6 +108,15 @@ class Recipes(SyncAPIResource, UseCaseResource): # type: ignore[misc]
91
108
  recipe_key: str,
92
109
  use_case: str | None = None,
93
110
  ) -> CustomRecipeData | None:
111
+ """Get details for a specific recipe.
112
+
113
+ Args:
114
+ recipe_key: The key or ID of the recipe.
115
+ use_case: Optional use case key. Falls back to client's default.
116
+
117
+ Returns:
118
+ The recipe data if found, otherwise None.
119
+ """
94
120
  return self._gql_client.get_custom_recipe(
95
121
  id_or_key=recipe_key, use_case=self.use_case_key(use_case)
96
122
  ).custom_recipe
@@ -105,6 +131,32 @@ class Recipes(SyncAPIResource, UseCaseResource): # type: ignore[misc]
105
131
  labels: Sequence[tuple[str, str]] | None = None,
106
132
  use_case: str | None = None,
107
133
  ) -> CustomRecipeData:
134
+ """Update an existing recipe.
135
+
136
+ Args:
137
+ recipe_key: The key of the recipe to update.
138
+ path: Optional new path to a Python file or directory to replace recipe code.
139
+ If None, only metadata (name, description, labels) is updated.
140
+ entrypoint: Optional path to the recipe entrypoint file, relative to the `path` directory.
141
+ **Only applicable when path is a directory.**
142
+
143
+ Raises ValueError if:
144
+ - path is a single file (entrypoint not supported for single files)
145
+ - path is a directory that already contains main.py
146
+
147
+ Raises FileNotFoundError if:
148
+ - the specified entrypoint file doesn't exist in the directory
149
+
150
+ If path is a directory and entrypoint is None:
151
+ - The directory must contain a main.py file, or FileNotFoundError is raised
152
+ name: Optional new display name.
153
+ description: Optional new description.
154
+ labels: Optional new key-value labels as tuples of (key, value).
155
+ use_case: Optional use case key. Falls back to client's default.
156
+
157
+ Returns:
158
+ The updated recipe data.
159
+ """
108
160
  label_inputs = [LabelInput(key=k, value=v) for k, v in labels] if labels else None
109
161
  input = UpdateRecipeInput(
110
162
  name=name,
@@ -133,11 +185,32 @@ class Recipes(SyncAPIResource, UseCaseResource): # type: ignore[misc]
133
185
  recipe_key: str,
134
186
  use_case: str | None = None,
135
187
  ) -> bool:
188
+ """Delete a recipe.
189
+
190
+ Args:
191
+ recipe_key: The key or ID of the recipe to delete.
192
+ use_case: Optional use case key. Falls back to client's default.
193
+
194
+ Returns:
195
+ True if deletion was successful.
196
+ """
136
197
  return self._gql_client.delete_custom_recipe(
137
198
  use_case=self.use_case_key(use_case), id=recipe_key
138
199
  ).delete_custom_recipe
139
200
 
140
201
  def generate_sample_input(self, recipe_key: str, use_case: str | None = None) -> dict:
202
+ """Generate a sample input dictionary based on the recipe's JSON schema.
203
+
204
+ Args:
205
+ recipe_key: The key or ID of the recipe.
206
+ use_case: Optional use case key. Falls back to client's default.
207
+
208
+ Returns:
209
+ A sample input dictionary conforming to the recipe's schema.
210
+
211
+ Raises:
212
+ ValueError: If the recipe is not found.
213
+ """
141
214
  recipe_details = self.get(recipe_key=recipe_key, use_case=self.use_case_key(use_case))
142
215
  if recipe_details is None:
143
216
  raise ValueError(f"Recipe {recipe_key} was not found")
@@ -174,6 +247,14 @@ class AsyncRecipes(AsyncAPIResource, UseCaseResource): # type: ignore[misc]
174
247
  UseCaseResource.__init__(self, client)
175
248
 
176
249
  async def list(self, use_case: str | None = None) -> Sequence[CustomRecipeData]:
250
+ """List all custom recipes for a use case.
251
+
252
+ Args:
253
+ use_case: Optional use case key. Falls back to client's default.
254
+
255
+ Returns:
256
+ A sequence of custom recipe data objects.
257
+ """
177
258
  filter = CustomRecipeFilterInput()
178
259
  return (
179
260
  await self._gql_client.list_custom_recipes(use_case=self.use_case_key(use_case), filter=filter)
@@ -193,18 +274,27 @@ class AsyncRecipes(AsyncAPIResource, UseCaseResource): # type: ignore[misc]
193
274
  Upload a recipe from either a single Python file or a directory (path).
194
275
 
195
276
  Args:
196
- path: Path to a Python file or directory containing the recipe
277
+ path: Path to a Python file or directory containing the recipe.
197
278
  recipe_key: Optional unique key for the recipe. If not provided, inferred from:
198
279
  - File name (without .py) if path is a file
199
280
  - "dir_name/entrypoint_name" if path is a directory and custom entrypoint is specified
200
281
  - Directory name if path is a directory and no custom entrypoint is specified
201
- entrypoint: Optional relative path to the entrypoint file within a directory.
202
- If specified, this file will be used as main.py. Cannot be used if
203
- main.py already exists in the directory, or if path is a file.
204
- name: Optional display name for the recipe
205
- description: Optional description
206
- labels: Optional key-value labels
207
- use_case: Optional use case identifier
282
+ entrypoint: Optional path to the recipe entrypoint file, relative to the `path` directory.
283
+ **Only applicable when path is a directory.**
284
+
285
+ Raises ValueError if:
286
+ - path is a single file (entrypoint not supported for single files)
287
+ - path is a directory that already contains main.py
288
+
289
+ Raises FileNotFoundError if:
290
+ - the specified entrypoint file doesn't exist in the directory
291
+
292
+ If path is a directory and entrypoint is None:
293
+ - The directory must contain a main.py file, or FileNotFoundError is raised
294
+ name: Optional display name for the recipe.
295
+ description: Optional description.
296
+ labels: Optional key-value labels as tuples of (key, value).
297
+ use_case: Optional use case identifier.
208
298
  """
209
299
  p = Path(path)
210
300
  if recipe_key is None:
@@ -232,6 +322,15 @@ class AsyncRecipes(AsyncAPIResource, UseCaseResource): # type: ignore[misc]
232
322
  recipe_key: str,
233
323
  use_case: str | None = None,
234
324
  ) -> CustomRecipeData | None:
325
+ """Get details for a specific recipe.
326
+
327
+ Args:
328
+ recipe_key: The key or ID of the recipe.
329
+ use_case: Optional use case key. Falls back to client's default.
330
+
331
+ Returns:
332
+ The recipe data if found, otherwise None.
333
+ """
235
334
  return (
236
335
  await self._gql_client.get_custom_recipe(id_or_key=recipe_key, use_case=self.use_case_key(use_case))
237
336
  ).custom_recipe
@@ -246,6 +345,32 @@ class AsyncRecipes(AsyncAPIResource, UseCaseResource): # type: ignore[misc]
246
345
  labels: Sequence[tuple[str, str]] | None = None,
247
346
  use_case: str | None = None,
248
347
  ) -> CustomRecipeData:
348
+ """Update an existing recipe.
349
+
350
+ Args:
351
+ recipe_key: The key or ID of the recipe to update.
352
+ path: Optional new path to a Python file or directory to replace recipe code.
353
+ If None, only metadata (name, description, labels) is updated.
354
+ entrypoint: Optional path to the recipe entrypoint file, relative to the `path` directory.
355
+ **Only applicable when path is a directory.**
356
+
357
+ Raises ValueError if:
358
+ - path is a single file (entrypoint not supported for single files)
359
+ - path is a directory that already contains main.py
360
+
361
+ Raises FileNotFoundError if:
362
+ - the specified entrypoint file doesn't exist in the directory
363
+
364
+ If path is a directory and entrypoint is None:
365
+ - The directory must contain a main.py file, or FileNotFoundError is raised
366
+ name: Optional new display name.
367
+ description: Optional new description.
368
+ labels: Optional new key-value labels as tuples of (key, value).
369
+ use_case: Optional use case key. Falls back to client's default.
370
+
371
+ Returns:
372
+ The updated recipe data.
373
+ """
249
374
  label_inputs = [LabelInput(key=k, value=v) for k, v in labels] if labels else None
250
375
  input = UpdateRecipeInput(
251
376
  name=name,
@@ -278,11 +403,32 @@ class AsyncRecipes(AsyncAPIResource, UseCaseResource): # type: ignore[misc]
278
403
  recipe_key: str,
279
404
  use_case: str | None = None,
280
405
  ) -> bool:
406
+ """Delete a recipe.
407
+
408
+ Args:
409
+ recipe_key: The key or ID of the recipe to delete.
410
+ use_case: Optional use case key. Falls back to client's default.
411
+
412
+ Returns:
413
+ True if deletion was successful.
414
+ """
281
415
  return (
282
416
  await self._gql_client.delete_custom_recipe(use_case=self.use_case_key(use_case), id=recipe_key)
283
417
  ).delete_custom_recipe
284
418
 
285
419
  async def generate_sample_input(self, recipe_key: str, use_case: str | None = None) -> dict:
420
+ """Generate a sample input dictionary based on the recipe's JSON schema.
421
+
422
+ Args:
423
+ recipe_key: The key or ID of the recipe.
424
+ use_case: Optional use case key. Falls back to client's default.
425
+
426
+ Returns:
427
+ A sample input dictionary conforming to the recipe's schema.
428
+
429
+ Raises:
430
+ ValueError: If the recipe is not found.
431
+ """
286
432
  recipe_details = await self.get(recipe_key=recipe_key, use_case=self.use_case_key(use_case))
287
433
  if recipe_details is None:
288
434
  raise ValueError(f"Recipe {recipe_key} was not found")
@@ -359,8 +505,8 @@ def _validate_recipe_directory(dir_path: Path, entrypoint: str | None = None) ->
359
505
  if entrypoint:
360
506
  if main_py_exists:
361
507
  raise ValueError(
362
- f"Cannot specify entrypoint when main.py already exists in directory: {dir_path}. "
363
- f"Either remove/rename main.py or use it directly without specifying an entrypoint."
508
+ "Cannot specify entrypoint when main.py already exists in directory: "
509
+ f"{dir_path}. Either remove/rename main.py or use it directly without specifying an entrypoint."
364
510
  )
365
511
  entrypoint_path = dir_path / entrypoint
366
512
  if not entrypoint_path.exists() or not entrypoint_path.is_file():
@@ -368,7 +514,7 @@ def _validate_recipe_directory(dir_path: Path, entrypoint: str | None = None) ->
368
514
  else:
369
515
  if not main_py_exists:
370
516
  raise FileNotFoundError(
371
- f"Directory must contain a 'main.py' file, or in alternative you must specify an `entrypoint` file"
517
+ "Directory must contain a 'main.py' file, or in alternative you must specify an `entrypoint` file"
372
518
  )
373
519
 
374
520
 
@@ -393,7 +539,7 @@ def _upload_from_path(path: str, entrypoint: str | None = None):
393
539
 
394
540
  if p.is_file():
395
541
  if entrypoint:
396
- raise ValueError(f"Entrypoint parameter is not supported for single file recipe uploads")
542
+ raise ValueError("Entrypoint parameter is not supported for single file recipe uploads")
397
543
  _validate_python_file(p)
398
544
  filename = p.name
399
545
  content_type = mimetypes.guess_type(str(p))[0] or "application/octet-stream"
@@ -408,7 +554,7 @@ def _upload_from_path(path: str, entrypoint: str | None = None):
408
554
  try:
409
555
  zip_buffer = _zip_directory_to_bytes_io(p, entrypoint=entrypoint)
410
556
  except Exception:
411
- logger.error(f"Failed to create in-memory zip for directory upload.")
557
+ logger.error("Failed to create in-memory zip for directory upload.")
412
558
  raise
413
559
  filename = f"{p.name}.zip"
414
560
  try:
@@ -1,9 +1,10 @@
1
1
  from __future__ import annotations
2
+
2
3
  from typing import TYPE_CHECKING, List
3
4
 
4
- from adaptive_sdk.graphql_client import ListRolesRoles, RoleCreate, CreateRoleCreateRole
5
+ from adaptive_sdk.graphql_client import CreateRoleCreateRole, ListRolesRoles, RoleCreate
5
6
 
6
- from .base_resource import SyncAPIResource, AsyncAPIResource, UseCaseResource
7
+ from .base_resource import AsyncAPIResource, SyncAPIResource, UseCaseResource
7
8
 
8
9
  if TYPE_CHECKING:
9
10
  from adaptive_sdk.client import Adaptive, AsyncAdaptive
@@ -19,11 +20,14 @@ class Roles(SyncAPIResource, UseCaseResource): # type: ignore[misc]
19
20
  UseCaseResource.__init__(self, client)
20
21
 
21
22
  def list(self) -> List[ListRolesRoles]:
23
+ """List all roles.
24
+
25
+ Returns:
26
+ A list of role objects.
27
+ """
22
28
  return self._gql_client.list_roles().roles
23
29
 
24
- def create(
25
- self, key: str, permissions: List[str], name: str | None = None
26
- ) -> CreateRoleCreateRole:
30
+ def create(self, key: str, permissions: List[str], name: str | None = None) -> CreateRoleCreateRole:
27
31
  """
28
32
  Creates new role.
29
33
 
@@ -46,11 +50,14 @@ class AsyncRoles(AsyncAPIResource, UseCaseResource): # type: ignore[misc]
46
50
  UseCaseResource.__init__(self, client)
47
51
 
48
52
  async def list(self) -> List[ListRolesRoles]:
53
+ """List all roles.
54
+
55
+ Returns:
56
+ A list of role objects.
57
+ """
49
58
  return (await self._gql_client.list_roles()).roles
50
59
 
51
- async def create(
52
- self, key: str, permissions: List[str], name: str | None = None
53
- ) -> CreateRoleCreateRole:
60
+ async def create(self, key: str, permissions: List[str], name: str | None = None) -> CreateRoleCreateRole:
54
61
  """
55
62
  Creates new role.
56
63
 
@@ -1,9 +1,10 @@
1
1
  from __future__ import annotations
2
+
2
3
  from typing import TYPE_CHECKING, List
3
4
 
4
5
  from adaptive_sdk.graphql_client import CreateTeamCreateTeam, ListTeamsTeams, TeamCreate
5
6
 
6
- from .base_resource import SyncAPIResource, AsyncAPIResource, UseCaseResource
7
+ from .base_resource import AsyncAPIResource, SyncAPIResource, UseCaseResource
7
8
 
8
9
  if TYPE_CHECKING:
9
10
  from adaptive_sdk.client import Adaptive, AsyncAdaptive
@@ -19,9 +20,23 @@ class Teams(SyncAPIResource, UseCaseResource): # type: ignore[misc]
19
20
  UseCaseResource.__init__(self, client)
20
21
 
21
22
  def list(self) -> List[ListTeamsTeams]:
23
+ """List all teams.
24
+
25
+ Returns:
26
+ A list of team objects.
27
+ """
22
28
  return self._gql_client.list_teams().teams
23
29
 
24
30
  def create(self, key: str, name: str | None = None) -> CreateTeamCreateTeam:
31
+ """Create a new team.
32
+
33
+ Args:
34
+ key: Unique key for the team.
35
+ name: Human-readable team name. If not provided, defaults to key.
36
+
37
+ Returns:
38
+ The created team data.
39
+ """
25
40
  input = TeamCreate(key=key, name=name or key)
26
41
  return self._gql_client.create_team(input).create_team
27
42
 
@@ -36,8 +51,22 @@ class AsyncTeams(AsyncAPIResource, UseCaseResource): # type: ignore[misc]
36
51
  UseCaseResource.__init__(self, client)
37
52
 
38
53
  async def list(self) -> List[ListTeamsTeams]:
54
+ """List all teams.
55
+
56
+ Returns:
57
+ A list of team objects.
58
+ """
39
59
  return (await self._gql_client.list_teams()).teams
40
60
 
41
61
  async def create(self, key: str, name: str | None = None) -> CreateTeamCreateTeam:
62
+ """Create a new team.
63
+
64
+ Args:
65
+ key: Unique key for the team.
66
+ name: Human-readable team name. If not provided, defaults to key.
67
+
68
+ Returns:
69
+ The created team data.
70
+ """
42
71
  input = TeamCreate(key=key, name=name or key)
43
72
  return (await self._gql_client.create_team(input)).create_team
@@ -1,16 +1,18 @@
1
1
  from __future__ import annotations
2
- from typing import Sequence, TYPE_CHECKING
2
+
3
+ from typing import TYPE_CHECKING, Sequence
4
+
3
5
  from loguru import logger
4
6
 
5
7
  from adaptive_sdk.graphql_client import (
6
8
  UseCaseCreate,
7
- UseCaseSettingsInput,
8
9
  UseCaseData,
9
- UseCaseShares,
10
+ UseCaseSettingsInput,
10
11
  UseCaseShareInput,
12
+ UseCaseShares,
11
13
  )
12
14
 
13
- from .base_resource import SyncAPIResource, AsyncAPIResource, UseCaseResource
15
+ from .base_resource import AsyncAPIResource, SyncAPIResource, UseCaseResource
14
16
 
15
17
  if TYPE_CHECKING:
16
18
  from adaptive_sdk.client import Adaptive, AsyncAdaptive
@@ -40,6 +42,7 @@ class UseCase(SyncAPIResource, UseCaseResource): # type: ignore[misc]
40
42
  name: Human-readable use case name which will be rendered in the UI.
41
43
  If not set, will be the same as `key`.
42
44
  description: Description of model which will be rendered in the UI.
45
+ team: Team key to associate the use case with.
43
46
  """
44
47
 
45
48
  input = UseCaseCreate(
@@ -155,7 +158,6 @@ class AsyncUseCase(AsyncAPIResource, UseCaseResource): # type: ignore[misc]
155
158
  name: str | None = None,
156
159
  description: str | None = None,
157
160
  team: str | None = None,
158
- default_feedback_key: str | None = None,
159
161
  ) -> UseCaseData:
160
162
  """
161
163
  Create new use case.
@@ -165,13 +167,14 @@ class AsyncUseCase(AsyncAPIResource, UseCaseResource): # type: ignore[misc]
165
167
  name: Human-readable use case name which will be rendered in the UI.
166
168
  If not set, will be the same as `key`.
167
169
  description: Description of model which will be rendered in the UI.
170
+ team: Team key to associate the use case with.
168
171
  """
169
172
  input = UseCaseCreate(
170
173
  name=name if name else key,
171
174
  key=key,
172
175
  description=description,
173
176
  team=team,
174
- settings=UseCaseSettingsInput(defaultMetric=default_feedback_key),
177
+ settings=UseCaseSettingsInput(defaultMetric=None),
175
178
  )
176
179
  result = await self._gql_client.create_use_case(input)
177
180
  return result.create_use_case
@@ -1,16 +1,17 @@
1
1
  from __future__ import annotations
2
+
2
3
  from typing import TYPE_CHECKING, Sequence
3
4
 
4
5
  from adaptive_sdk.graphql_client import (
5
- UserData,
6
+ TeamMemberRemove,
6
7
  TeamMemberSet,
7
8
  UpdateUserSetTeamMember,
8
- TeamMemberRemove,
9
9
  UserCreate,
10
10
  UserCreateTeamWithRole,
11
+ UserData,
11
12
  )
12
13
 
13
- from .base_resource import SyncAPIResource, AsyncAPIResource
14
+ from .base_resource import AsyncAPIResource, SyncAPIResource
14
15
 
15
16
  if TYPE_CHECKING:
16
17
  from adaptive_sdk.client import Adaptive, AsyncAdaptive
@@ -36,20 +37,23 @@ class Users(SyncAPIResource): # type: ignore[misc]
36
37
  """
37
38
  return self._gql_client.list_users().users
38
39
 
39
- def create(
40
- self, email: str, name: str, teams_with_role: Sequence[tuple[str, str]]
41
- ) -> UserData:
40
+ def create(self, email: str, name: str, teams_with_role: Sequence[tuple[str, str]]) -> UserData:
42
41
  """
43
- Create a user and with preset teams and role
42
+ Create a user with preset teams and roles.
43
+
44
+ Args:
45
+ email: User's email address.
46
+ name: User's display name.
47
+ teams_with_role: Sequence of (team_key, role_key) tuples assigning the user to teams with specific roles.
48
+
49
+ Returns:
50
+ The created user data.
44
51
  """
45
52
  return self._gql_client.create_user(
46
53
  input=UserCreate(
47
54
  email=email,
48
55
  name=name,
49
- teams=[
50
- UserCreateTeamWithRole(team=team, role=role)
51
- for (team, role) in teams_with_role
52
- ],
56
+ teams=[UserCreateTeamWithRole(team=team, role=role) for (team, role) in teams_with_role],
53
57
  )
54
58
  ).create_user
55
59
 
@@ -78,6 +82,11 @@ class Users(SyncAPIResource): # type: ignore[misc]
78
82
  return self._gql_client.remove_team_member(input).remove_team_member
79
83
 
80
84
  def delete(self, email: str):
85
+ """Delete a user from the system.
86
+
87
+ Args:
88
+ email: The email address of the user to delete.
89
+ """
81
90
  self._gql_client.delete_user(email)
82
91
 
83
92
 
@@ -103,27 +112,28 @@ class AsyncUsers(AsyncAPIResource): # type: ignore[misc]
103
112
  result = await self._gql_client.list_users()
104
113
  return result.users
105
114
 
106
- async def create(
107
- self, email: str, name: str, teams_with_role: Sequence[tuple[str, str]]
108
- ) -> UserData:
115
+ async def create(self, email: str, name: str, teams_with_role: Sequence[tuple[str, str]]) -> UserData:
109
116
  """
110
- Create a user and with preset teams and role
117
+ Create a user with preset teams and roles.
118
+
119
+ Args:
120
+ email: User's email address.
121
+ name: User's display name.
122
+ teams_with_role: Sequence of (team_key, role_key) tuples assigning the user to teams with specific roles.
123
+
124
+ Returns:
125
+ The created user data.
111
126
  """
112
127
  result = await self._gql_client.create_user(
113
128
  input=UserCreate(
114
129
  email=email,
115
130
  name=name,
116
- teams=[
117
- UserCreateTeamWithRole(team=team, role=role)
118
- for (team, role) in teams_with_role
119
- ],
131
+ teams=[UserCreateTeamWithRole(team=team, role=role) for (team, role) in teams_with_role],
120
132
  )
121
133
  )
122
134
  return result.create_user
123
135
 
124
- async def add_to_team(
125
- self, email: str, team: str, role: str
126
- ) -> UpdateUserSetTeamMember:
136
+ async def add_to_team(self, email: str, team: str, role: str) -> UpdateUserSetTeamMember:
127
137
  """
128
138
  Update team and role for user.
129
139
 
@@ -149,4 +159,9 @@ class AsyncUsers(AsyncAPIResource): # type: ignore[misc]
149
159
  return (await self._gql_client.remove_team_member(input)).remove_team_member
150
160
 
151
161
  async def delete(self, email: str):
162
+ """Delete a user from the system.
163
+
164
+ Args:
165
+ email: The email address of the user to delete.
166
+ """
152
167
  await self._gql_client.delete_user(email)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: adaptive-sdk
3
- Version: 0.1.14
3
+ Version: 0.12.1
4
4
  Summary: Python SDK for Adaptive Engine
5
5
  Author-email: Vincent Debergue <vincent@adaptive-ml.com>, Joao Moura <joao@adaptive-ml.com>, Yacine Bouraoui <yacine@adaptive-ml.com>
6
6
  Requires-Python: >=3.10