ayon-python-api 1.2.16.dev0__py3-none-any.whl → 1.2.17.dev0__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.
@@ -2,7 +2,7 @@ from __future__ import annotations
2
2
 
3
3
  import json
4
4
  import typing
5
- from typing import Optional, Iterable, Generator, Any
5
+ from typing import Optional, Iterable, Generator, Any, Literal
6
6
 
7
7
  from ayon_api.utils import (
8
8
  SortOrder,
@@ -255,6 +255,117 @@ class ActivitiesAPI(BaseServerAPI):
255
255
  )
256
256
  response.raise_for_status()
257
257
 
258
+ def get_raw_activity_categories(self, project_name: str) -> dict[str, Any]:
259
+ """Get activity categories available on server (raw response).
260
+
261
+ Args:
262
+ project_name (str): Project name to get categories for.
263
+
264
+ Returns:
265
+ list[str]: Available activity categories.
266
+
267
+ """
268
+ response = self.get(f"projects/{project_name}/activityCategories")
269
+ response.raise_for_status()
270
+ return response.data
271
+
272
+ def get_activity_categories(self, project_name: str) -> list[str]:
273
+ """Get activity categories available on server.
274
+
275
+ Args:
276
+ project_name (str): Project name to get categories for.
277
+
278
+ Returns:
279
+ list[str]: Available activity categories.
280
+
281
+ """
282
+
283
+ data = self.get_raw_activity_categories(project_name)
284
+ return data["categories"]
285
+
286
+ def create_activity_reaction(
287
+ self,
288
+ project_name: str,
289
+ activity_id: str,
290
+ reaction: str,
291
+ ) -> None:
292
+ """React to activity."""
293
+ response = self.post(
294
+ f"projects/{project_name}/activities/{activity_id}/reactions",
295
+ reaction=reaction,
296
+ )
297
+ response.raise_for_status()
298
+
299
+ def delete_activity_reaction(
300
+ self, project_name: str, activity_id: str, reaction: str
301
+ ) -> None:
302
+ response = self.delete(
303
+ f"projects/{project_name}/activities/{activity_id}"
304
+ f"/reactions/{reaction}"
305
+ )
306
+ response.raise_for_status()
307
+
308
+ def suggest_entity_mention(
309
+ self,
310
+ project_name: str,
311
+ entity_id: str,
312
+ entity_type: Literal["folder", "task", "version"],
313
+ ) -> dict[str, dict[str, Any]]:
314
+ """Suggest entities for mention in activity body.
315
+
316
+ At this moment does not change data only returns suggestions.
317
+
318
+ Args:
319
+ project_name (str): Project name to search in.
320
+ entity_id (str): Entity id.
321
+ entity_type (str): Entity type of the entity.
322
+
323
+ Returns:
324
+ list[dict[str, Any]]: List of suggested entities with
325
+ their details.
326
+
327
+ """
328
+ response = self.post(
329
+ f"projects/{project_name}/suggest",
330
+ entity_id=entity_id,
331
+ entity_type=entity_type,
332
+ )
333
+ response.raise_for_status()
334
+ return response.data
335
+
336
+ def get_raw_entity_watchers(
337
+ self, project_name: str, entity_id: str, entity_type: str
338
+ ) -> dict[str, Any]:
339
+ """Get entity watchers (raw response)."""
340
+ response = self.get(
341
+ f"projects/{project_name}/{entity_type}/{entity_id}/watchers"
342
+ )
343
+ response.raise_for_status()
344
+ return response.data
345
+
346
+ def get_entity_watchers(
347
+ self, project_name: str, entity_id: str, entity_type: str
348
+ ) -> list[str]:
349
+ """List watchers of an entity."""
350
+ data = self.get_raw_entity_watchers(
351
+ project_name, entity_id, entity_type
352
+ )
353
+ return data["watchers"]
354
+
355
+ def set_entity_watchers(
356
+ self,
357
+ project_name: str,
358
+ entity_id: str,
359
+ entity_type: str,
360
+ watchers: list[str],
361
+ ):
362
+ """Change watchers of an entity."""
363
+ response = self.post(
364
+ f"projects/{project_name}/{entity_type}/{entity_id}/watchers",
365
+ watchers=watchers,
366
+ )
367
+ response.raise_for_status()
368
+
258
369
  def send_activities_batch_operations(
259
370
  self,
260
371
  project_name: str,
@@ -139,7 +139,7 @@ class AttributesAPI(BaseServerAPI):
139
139
 
140
140
  self.reset_attributes_schema()
141
141
 
142
- def remove_attribute_config(self, attribute_name: str) -> None:
142
+ def delete_attribute_config(self, attribute_name: str) -> None:
143
143
  """Remove attribute from server.
144
144
 
145
145
  This can't be un-done, please use carefully.
@@ -156,6 +156,17 @@ class AttributesAPI(BaseServerAPI):
156
156
 
157
157
  self.reset_attributes_schema()
158
158
 
159
+ def remove_attribute_config(self, attribute_name: str) -> None:
160
+ """Remove attribute from server.
161
+
162
+ DEPRECATED: Use 'delete_attribute_config' instead.
163
+
164
+ Args:
165
+ attribute_name (str): Name of attribute to remove.
166
+
167
+ """
168
+ return self.delete_attribute_config(attribute_name)
169
+
159
170
  def get_attributes_for_type(
160
171
  self, entity_type: AttributeScope
161
172
  ) -> dict[str, AttributeSchemaDataDict]:
@@ -316,6 +316,7 @@ class ListsAPI(BaseServerAPI):
316
316
  self,
317
317
  project_name: str,
318
318
  list_id: str,
319
+ entity_id: str,
319
320
  *,
320
321
  position: Optional[int] = None,
321
322
  label: Optional[str] = None,
@@ -329,6 +330,7 @@ class ListsAPI(BaseServerAPI):
329
330
  Args:
330
331
  project_name (str): Project name where entity list lives.
331
332
  list_id (str): Entity list id where item will be added.
333
+ entity_id (str): Id of entity added to the list.
332
334
  position (Optional[int]): Position of item in entity list.
333
335
  label (Optional[str]): Label of item in entity list.
334
336
  attrib (Optional[dict[str, Any]]): Item attribute values.
@@ -342,9 +344,10 @@ class ListsAPI(BaseServerAPI):
342
344
  """
343
345
  if item_id is None:
344
346
  item_id = create_entity_id()
347
+
345
348
  kwargs = {
346
349
  "id": item_id,
347
- "entityId": list_id,
350
+ "entityId": entity_id,
348
351
  }
349
352
  for key, value in (
350
353
  ("position", position),
@@ -174,6 +174,7 @@ class ProjectsAPI(BaseServerAPI):
174
174
  self,
175
175
  active: Optional[bool] = True,
176
176
  library: Optional[bool] = None,
177
+ include_skeleton: bool = False,
177
178
  ) -> Generator[ProjectDict, None, None]:
178
179
  """Query available project entities.
179
180
 
@@ -184,12 +185,17 @@ class ProjectsAPI(BaseServerAPI):
184
185
  are returned if 'None' is passed.
185
186
  library (Optional[bool]): Filter standard/library projects. Both
186
187
  are returned if 'None' is passed.
188
+ include_skeleton (bool): Include skeleton projects.
187
189
 
188
190
  Returns:
189
191
  Generator[ProjectDict, None, None]: Available projects.
190
192
 
191
193
  """
192
- for project_name in self.get_project_names(active, library):
194
+ for project_name in self.get_project_names(
195
+ active=active,
196
+ library=library,
197
+ include_skeleton=include_skeleton,
198
+ ):
193
199
  project = self.get_rest_project(project_name)
194
200
  if project:
195
201
  yield project
@@ -198,6 +204,7 @@ class ProjectsAPI(BaseServerAPI):
198
204
  self,
199
205
  active: Optional[bool] = True,
200
206
  library: Optional[bool] = None,
207
+ include_skeleton: bool = False,
201
208
  ) -> list[ProjectListDict]:
202
209
  """Receive available projects.
203
210
 
@@ -208,6 +215,7 @@ class ProjectsAPI(BaseServerAPI):
208
215
  are returned if 'None' is passed.
209
216
  library (Optional[bool]): Filter standard/library projects. Both
210
217
  are returned if 'None' is passed.
218
+ include_skeleton (bool): Include skeleton projects.
211
219
 
212
220
  Returns:
213
221
  list[ProjectListDict]: List of available projects.
@@ -219,10 +227,14 @@ class ProjectsAPI(BaseServerAPI):
219
227
  if library is not None:
220
228
  library = "true" if library else "false"
221
229
 
222
- query = prepare_query_string({
230
+ query_data = {
223
231
  "active": active,
224
232
  "library": library,
225
- })
233
+ }
234
+ if include_skeleton:
235
+ query_data["skeleton"] = "true"
236
+
237
+ query = prepare_query_string(query_data)
226
238
  response = self.get(f"projects{query}")
227
239
  response.raise_for_status()
228
240
  data = response.data
@@ -232,6 +244,7 @@ class ProjectsAPI(BaseServerAPI):
232
244
  self,
233
245
  active: Optional[bool] = True,
234
246
  library: Optional[bool] = None,
247
+ include_skeleton: bool = False,
235
248
  ) -> list[str]:
236
249
  """Receive available project names.
237
250
 
@@ -242,6 +255,7 @@ class ProjectsAPI(BaseServerAPI):
242
255
  are returned if 'None' is passed.
243
256
  library (Optional[bool]): Filter standard/library projects. Both
244
257
  are returned if 'None' is passed.
258
+ include_skeleton (bool): Include skeleton projects.
245
259
 
246
260
  Returns:
247
261
  list[str]: List of available project names.
@@ -249,13 +263,18 @@ class ProjectsAPI(BaseServerAPI):
249
263
  """
250
264
  return [
251
265
  project["name"]
252
- for project in self.get_rest_projects_list(active, library)
266
+ for project in self.get_rest_projects_list(
267
+ active=active,
268
+ library=library,
269
+ include_skeleton=include_skeleton,
270
+ )
253
271
  ]
254
272
 
255
273
  def get_projects(
256
274
  self,
257
275
  active: Optional[bool] = True,
258
276
  library: Optional[bool] = None,
277
+ include_skeleton: bool = False,
259
278
  fields: Optional[Iterable[str]] = None,
260
279
  own_attributes: bool = False,
261
280
  ) -> Generator[ProjectDict, None, None]:
@@ -266,6 +285,7 @@ class ProjectsAPI(BaseServerAPI):
266
285
  Filter is disabled when 'None' is passed.
267
286
  library (Optional[bool]): Filter library projects. Filter is
268
287
  disabled when 'None' is passed.
288
+ include_skeleton (bool): Include skeleton projects.
269
289
  fields (Optional[Iterable[str]]): fields to be queried
270
290
  for project.
271
291
  own_attributes (Optional[bool]): Attribute values that are
@@ -280,7 +300,11 @@ class ProjectsAPI(BaseServerAPI):
280
300
 
281
301
  graphql_fields, fetch_type = self._get_project_graphql_fields(fields)
282
302
  if fetch_type == ProjectFetchType.RESTList:
283
- yield from self.get_rest_projects_list(active, library)
303
+ yield from self.get_rest_projects_list(
304
+ active=active,
305
+ library=library,
306
+ include_skeleton=include_skeleton,
307
+ )
284
308
  return
285
309
 
286
310
  projects_by_name = {}
@@ -288,6 +312,7 @@ class ProjectsAPI(BaseServerAPI):
288
312
  projects = list(self._get_graphql_projects(
289
313
  active,
290
314
  library,
315
+ include_skeleton=include_skeleton,
291
316
  fields=graphql_fields,
292
317
  own_attributes=own_attributes,
293
318
  ))
@@ -296,7 +321,11 @@ class ProjectsAPI(BaseServerAPI):
296
321
  return
297
322
  projects_by_name = {p["name"]: p for p in projects}
298
323
 
299
- for project in self.get_rest_projects(active=active, library=library):
324
+ for project in self.get_rest_projects(
325
+ active=active,
326
+ library=library,
327
+ include_skeleton=include_skeleton,
328
+ ):
300
329
  if own_attributes:
301
330
  fill_own_attribs(project)
302
331
 
@@ -356,6 +385,8 @@ class ProjectsAPI(BaseServerAPI):
356
385
  project_code: str,
357
386
  library_project: bool = False,
358
387
  preset_name: Optional[str] = None,
388
+ data: dict[str, Any] | None = None,
389
+ skeleton: bool = False,
359
390
  ) -> ProjectDict:
360
391
  """Create project using AYON settings.
361
392
 
@@ -375,6 +406,8 @@ class ProjectsAPI(BaseServerAPI):
375
406
  library_project (Optional[bool]): Project is library project.
376
407
  preset_name (Optional[str]): Name of anatomy preset. Default is
377
408
  used if not passed.
409
+ data (dict[str, Any]): Project data.
410
+ skeleton (bool): Project is skeleton project.
378
411
 
379
412
  Raises:
380
413
  ValueError: When project name already exists.
@@ -395,12 +428,19 @@ class ProjectsAPI(BaseServerAPI):
395
428
 
396
429
  preset = self.get_project_anatomy_preset(preset_name)
397
430
 
431
+ if data is None:
432
+ data = {}
433
+
434
+ if skeleton:
435
+ data["skeleton"] = True
436
+
398
437
  result = self.post(
399
438
  "projects",
400
439
  name=project_name,
401
440
  code=project_code,
402
441
  anatomy=preset,
403
- library=library_project
442
+ library=library_project,
443
+ data=data,
404
444
  )
405
445
 
406
446
  if result.status != 201:
@@ -498,6 +538,73 @@ class ProjectsAPI(BaseServerAPI):
498
538
  f"Failed to delete project \"{project_name}\". {detail}"
499
539
  )
500
540
 
541
+ def get_raw_project_folders(self) -> dict[str, Any]:
542
+ """Get project folders (raw data)."""
543
+ response = self.get("projectFolders")
544
+ response.raise_for_status()
545
+ return response.data
546
+
547
+ def get_project_folders(self) -> list[dict[str, Any]]:
548
+ data = self.get_raw_project_folders()
549
+ return data["folders"]
550
+
551
+ def create_project_folder(
552
+ self,
553
+ label: str,
554
+ parent_id: str | None = None,
555
+ data: dict[str, Any] | None = None,
556
+ ) -> str:
557
+ """Create project folder."""
558
+ kwargs = {}
559
+ if parent_id is not None:
560
+ kwargs["parentId"] = parent_id
561
+ if data:
562
+ kwargs["data"] = data
563
+
564
+ response = self.post("projectFolders", label=label, **kwargs)
565
+ response.raise_for_status()
566
+ return response.data["id"]
567
+
568
+ def update_project_folder(
569
+ self,
570
+ folder_id: str,
571
+ label: str | None = None,
572
+ parent_id: str | None = None,
573
+ data: dict[str, Any] | None = None,
574
+ ) -> None:
575
+ body = {
576
+ key: value
577
+ for key, value in (
578
+ ("label", label),
579
+ ("parentId", parent_id),
580
+ ("data", data),
581
+ )
582
+ if value is not None
583
+ }
584
+ response = self.patch(f"projectFolders/{folder_id}", **body)
585
+ response.raise_for_status()
586
+
587
+ def set_project_folders_order(self, folder_ids: list[str]) -> None:
588
+ """Set project folders order."""
589
+ response = self.post("projectFolders/order", order=folder_ids)
590
+ response.raise_for_status()
591
+
592
+ def assign_projects_to_project_folder(
593
+ self, folder_id: str, project_names: list[str],
594
+ ) -> None:
595
+ """Assign project folder to project."""
596
+ response = self.post(
597
+ "projectFolders/assign",
598
+ folderId=folder_id,
599
+ projectNames=project_names,
600
+ )
601
+ response.raise_for_status()
602
+
603
+ def delete_project_folder(self, folder_id: str):
604
+ """Delete project folder."""
605
+ response = self.delete(f"projectFolders/{folder_id}")
606
+ response.raise_for_status()
607
+
501
608
  def get_project_root_overrides(
502
609
  self, project_name: str
503
610
  ) -> dict[str, dict[str, str]]:
@@ -792,8 +899,9 @@ class ProjectsAPI(BaseServerAPI):
792
899
 
793
900
  def _get_graphql_projects(
794
901
  self,
795
- active: Optional[bool],
796
- library: Optional[bool],
902
+ active: bool | None,
903
+ library: bool | None,
904
+ include_skeleton: bool,
797
905
  fields: set[str],
798
906
  own_attributes: bool,
799
907
  project_name: Optional[str] = None
@@ -810,6 +918,9 @@ class ProjectsAPI(BaseServerAPI):
810
918
  if project_name is not None:
811
919
  query.set_variable_value("projectName", project_name)
812
920
 
921
+ if include_skeleton:
922
+ query.set_variable_value("skeleton", True)
923
+
813
924
  attributes = {}
814
925
  if "allAttrib" in fields:
815
926
  attributes = self.get_attributes_for_type("project")
ayon_api/exceptions.py CHANGED
@@ -1,3 +1,5 @@
1
+ from __future__ import annotations
2
+
1
3
  import copy
2
4
 
3
5
  try:
@@ -21,13 +23,23 @@ class UrlError(Exception):
21
23
  UI if needed.
22
24
  """
23
25
 
24
- def __init__(self, message, title, hints=None):
26
+
27
+ def __init__(
28
+ self,
29
+ message: str,
30
+ title: str,
31
+ hints: list[str] | None = None,
32
+ ) -> None:
25
33
  if hints is None:
26
34
  hints = []
27
35
 
28
36
  self.title = title
29
37
  self.hints = hints
30
- super(UrlError, self).__init__(message)
38
+ super().__init__(message)
39
+
40
+
41
+ class UrlNotReached(UrlError):
42
+ pass
31
43
 
32
44
 
33
45
  class ServerError(Exception):
@@ -91,8 +91,10 @@ def project_graphql_query(fields):
91
91
  def projects_graphql_query(fields):
92
92
  query = GraphQlQuery("ProjectsQuery")
93
93
  project_name_var = query.add_variable("projectName", "String!")
94
+ skeleton_var = query.add_variable("skeleton", "Boolean!")
94
95
  projects_field = query.add_field_with_edges("projects")
95
96
  projects_field.set_filter("name", project_name_var)
97
+ projects_field.set_filter("includeSkeleton", skeleton_var)
96
98
 
97
99
  nested_fields = fields_to_dict(fields)
98
100
 
ayon_api/server_api.py CHANGED
@@ -16,7 +16,7 @@ import platform
16
16
  import uuid
17
17
  from contextlib import contextmanager
18
18
  import typing
19
- from typing import Optional, Iterable, Generator, Any, Union
19
+ from typing import Optional, Iterable, Generator, Any, Union, Literal
20
20
 
21
21
  import requests
22
22
 
@@ -1361,6 +1361,176 @@ class ServerAPI(
1361
1361
  def delete(self, entrypoint: str, **kwargs):
1362
1362
  return self.raw_delete(entrypoint, params=kwargs)
1363
1363
 
1364
+ def get_server_config(self):
1365
+ response = self.get("config")
1366
+ response.raise_for_status()
1367
+ return response.data
1368
+
1369
+ def set_server_config(
1370
+ self,
1371
+ studio_name: str | None = None,
1372
+ customization: dict[str, Any] | None = None,
1373
+ authentication: dict[str, Any] | None = None,
1374
+ project_options: dict[str, Any] | None = None,
1375
+ changelog: dict[str, Any] | None = None,
1376
+ ) -> None:
1377
+ body = {
1378
+ key: value
1379
+ for key, value in (
1380
+ ("studio_name", studio_name),
1381
+ ("customization", customization),
1382
+ ("authentication", authentication),
1383
+ ("project_options", project_options),
1384
+ ("changelog", changelog),
1385
+ )
1386
+ if value is not None
1387
+ }
1388
+ response = self.post("config", **body)
1389
+ response.raise_for_status()
1390
+
1391
+ def get_server_config_overrides(self):
1392
+ response = self.get("config/overrides")
1393
+ response.raise_for_status()
1394
+ return response.data
1395
+
1396
+ def get_server_config_value(self, key: str):
1397
+ response = self.get(f"config/value/{key}")
1398
+ response.raise_for_status()
1399
+ return response.data
1400
+
1401
+ def download_server_config_file(
1402
+ self,
1403
+ file_type: Literal["login_background", "studio_logo"],
1404
+ filepath: str,
1405
+ *,
1406
+ chunk_size: Optional[int] = None,
1407
+ progress: Optional[TransferProgress] = None,
1408
+ ) -> TransferProgress:
1409
+ """Download server config file.
1410
+
1411
+ Validate if server has config file available first. Method crashes
1412
+ if the file is not available.
1413
+
1414
+ Args:
1415
+ file_type (Literal["login_background", "studio_logo"]): File to
1416
+ download.
1417
+ filepath (str): Target filepath.
1418
+ chunk_size (int | None): Size of chunks used for download.
1419
+ progress (TransferProgress | None): Object to track download
1420
+ progress.
1421
+
1422
+ """
1423
+ return self.download_file(
1424
+ f"api/config/files/{file_type}",
1425
+ filepath,
1426
+ chunk_size=chunk_size,
1427
+ progress=progress,
1428
+ )
1429
+
1430
+ def download_server_config_file_to_stream(
1431
+ self,
1432
+ file_type: Literal["login_background", "studio_logo"],
1433
+ stream: StreamType,
1434
+ *,
1435
+ chunk_size: Optional[int] = None,
1436
+ progress: Optional[TransferProgress] = None,
1437
+ ) -> TransferProgress:
1438
+ """Download server config file to byte stream.
1439
+
1440
+ Validate if server has config file available first. Method crashes
1441
+ if the file is not available.
1442
+
1443
+ Args:
1444
+ file_type (Literal["login_background", "studio_logo"]): File to
1445
+ download.
1446
+ stream (StreamType): Stream where downloaded content is stored.
1447
+ chunk_size (int | None): Size of chunks used for download.
1448
+ progress (TransferProgress | None): Object to track download
1449
+ progress.
1450
+
1451
+ """
1452
+ return self.download_file_to_stream(
1453
+ f"api/config/files/{file_type}",
1454
+ stream,
1455
+ chunk_size=chunk_size,
1456
+ progress=progress,
1457
+ )
1458
+
1459
+ def upload_server_config_file(
1460
+ self,
1461
+ file_type: Literal["login_background", "studio_logo"],
1462
+ filepath: str,
1463
+ *,
1464
+ content_type: str | None = None,
1465
+ filename: str | None = None,
1466
+ chunk_size: int | None = None,
1467
+ progress: TransferProgress | None = None,
1468
+ ) -> requests.Response:
1469
+ """Upload server config file from byte stream.
1470
+
1471
+ TODO create filename using file_type and extension from content_type
1472
+ if filename is not specified
1473
+
1474
+ Args:
1475
+ file_type (Literal["login_background", "studio_logo"]): File to
1476
+ download.
1477
+ filepath (str): Filepath used to store the file.
1478
+ chunk_size (int | None): Size of chunks used for download.
1479
+ progress (TransferProgress | None): Object to track download
1480
+ progress.
1481
+
1482
+ Returns:
1483
+ requests.Response: Response from upload.
1484
+
1485
+ """
1486
+ if not filename:
1487
+ filename = os.path.basename(filepath)
1488
+ return self.upload_file(
1489
+ f"api/config/files/{file_type}",
1490
+ filepath,
1491
+ filename=filename,
1492
+ content_type=content_type,
1493
+ chunk_size=chunk_size,
1494
+ progress=progress,
1495
+ )
1496
+
1497
+ def upload_server_config_file_from_stream(
1498
+ self,
1499
+ file_type: Literal["login_background", "studio_logo"],
1500
+ stream: StreamType,
1501
+ filename: str,
1502
+ *,
1503
+ content_type: str | None = None,
1504
+ chunk_size: int | None = None,
1505
+ progress: TransferProgress | None = None,
1506
+ ) -> requests.Response:
1507
+ """Upload server config file from byte stream.
1508
+
1509
+ TODO create filename using file_type and extension from content_type
1510
+ if filename is not specified
1511
+
1512
+ Args:
1513
+ file_type (Literal["login_background", "studio_logo"]): File to
1514
+ download.
1515
+ stream (StreamType): Stream where downloaded content is stored.
1516
+ filename (str): Filename used to store the file.
1517
+ chunk_size (int | None): Size of chunks used for download.
1518
+ progress (TransferProgress | None): Object to track download
1519
+ progress.
1520
+
1521
+ Returns:
1522
+ requests.Response: Response from upload.
1523
+
1524
+ """
1525
+ return self.upload_file_from_stream(
1526
+ f"api/config/files/{file_type}",
1527
+ stream,
1528
+ filename=filename,
1529
+ content_type=content_type,
1530
+ chunk_size=chunk_size,
1531
+ progress=progress,
1532
+ )
1533
+
1364
1534
  def _endpoint_to_url(
1365
1535
  self,
1366
1536
  endpoint: str,
ayon_api/typing.py CHANGED
@@ -103,7 +103,8 @@ AttributeScope = Literal[
103
103
  "version",
104
104
  "representation",
105
105
  "workfile",
106
- "user"
106
+ "user",
107
+ "list",
107
108
  ]
108
109
 
109
110
  AttributeType = Literal[