ayon-python-api 1.0.12__tar.gz → 1.1.0__tar.gz

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.
Files changed (24) hide show
  1. {ayon-python-api-1.0.12 → ayon-python-api-1.1.0}/PKG-INFO +1 -1
  2. {ayon-python-api-1.0.12 → ayon-python-api-1.1.0}/ayon_api/__init__.py +2 -0
  3. {ayon-python-api-1.0.12 → ayon-python-api-1.1.0}/ayon_api/_api.py +37 -4
  4. {ayon-python-api-1.0.12 → ayon-python-api-1.1.0}/ayon_api/constants.py +1 -0
  5. {ayon-python-api-1.0.12 → ayon-python-api-1.1.0}/ayon_api/entity_hub.py +8 -70
  6. {ayon-python-api-1.0.12 → ayon-python-api-1.1.0}/ayon_api/graphql_queries.py +4 -0
  7. {ayon-python-api-1.0.12 → ayon-python-api-1.1.0}/ayon_api/operations.py +22 -4
  8. {ayon-python-api-1.0.12 → ayon-python-api-1.1.0}/ayon_api/server_api.py +110 -150
  9. {ayon-python-api-1.0.12 → ayon-python-api-1.1.0}/ayon_api/utils.py +7 -5
  10. {ayon-python-api-1.0.12 → ayon-python-api-1.1.0}/ayon_api/version.py +1 -1
  11. {ayon-python-api-1.0.12 → ayon-python-api-1.1.0}/ayon_python_api.egg-info/PKG-INFO +1 -1
  12. {ayon-python-api-1.0.12 → ayon-python-api-1.1.0}/ayon_python_api.egg-info/requires.txt +0 -1
  13. {ayon-python-api-1.0.12 → ayon-python-api-1.1.0}/pyproject.toml +2 -4
  14. {ayon-python-api-1.0.12 → ayon-python-api-1.1.0}/LICENSE +0 -0
  15. {ayon-python-api-1.0.12 → ayon-python-api-1.1.0}/README.md +0 -0
  16. {ayon-python-api-1.0.12 → ayon-python-api-1.1.0}/ayon_api/events.py +0 -0
  17. {ayon-python-api-1.0.12 → ayon-python-api-1.1.0}/ayon_api/exceptions.py +0 -0
  18. {ayon-python-api-1.0.12 → ayon-python-api-1.1.0}/ayon_api/graphql.py +0 -0
  19. {ayon-python-api-1.0.12 → ayon-python-api-1.1.0}/ayon_api/typing.py +0 -0
  20. {ayon-python-api-1.0.12 → ayon-python-api-1.1.0}/ayon_python_api.egg-info/SOURCES.txt +0 -0
  21. {ayon-python-api-1.0.12 → ayon-python-api-1.1.0}/ayon_python_api.egg-info/dependency_links.txt +0 -0
  22. {ayon-python-api-1.0.12 → ayon-python-api-1.1.0}/ayon_python_api.egg-info/top_level.txt +0 -0
  23. {ayon-python-api-1.0.12 → ayon-python-api-1.1.0}/setup.cfg +0 -0
  24. {ayon-python-api-1.0.12 → ayon-python-api-1.1.0}/setup.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: ayon-python-api
3
- Version: 1.0.12
3
+ Version: 1.1.0
4
4
  Summary: AYON Python API
5
5
  Home-page: https://github.com/ynput/ayon-python-api
6
6
  Author: ynput.io
@@ -214,6 +214,7 @@ from ._api import (
214
214
  get_thumbnail_by_id,
215
215
  get_thumbnail,
216
216
  get_folder_thumbnail,
217
+ get_task_thumbnail,
217
218
  get_version_thumbnail,
218
219
  get_workfile_thumbnail,
219
220
  create_thumbnail,
@@ -459,6 +460,7 @@ __all__ = (
459
460
  "get_thumbnail_by_id",
460
461
  "get_thumbnail",
461
462
  "get_folder_thumbnail",
463
+ "get_task_thumbnail",
462
464
  "get_version_thumbnail",
463
465
  "get_workfile_thumbnail",
464
466
  "create_thumbnail",
@@ -706,6 +706,7 @@ def get_server_version_tuple() -> Tuple[int, int, int, str, str]:
706
706
  def get_users(
707
707
  project_name: Optional[str] = None,
708
708
  usernames: Optional[Iterable[str]] = None,
709
+ emails: Optional[Iterable[str]] = None,
709
710
  fields: Optional[Iterable[str]] = None,
710
711
  ) -> Generator[Dict[str, Any], None, None]:
711
712
  """Get Users.
@@ -716,6 +717,7 @@ def get_users(
716
717
  Args:
717
718
  project_name (Optional[str]): Project name.
718
719
  usernames (Optional[Iterable[str]]): Filter by usernames.
720
+ emails (Optional[Iterable[str]]): Filter by emails.
719
721
  fields (Optional[Iterable[str]]): Fields to be queried
720
722
  for users.
721
723
 
@@ -727,6 +729,7 @@ def get_users(
727
729
  return con.get_users(
728
730
  project_name=project_name,
729
731
  usernames=usernames,
732
+ emails=emails,
730
733
  fields=fields,
731
734
  )
732
735
 
@@ -5472,6 +5475,7 @@ def create_representation(
5472
5475
  files: Optional[List[Dict[str, Any]]] = None,
5473
5476
  attrib: Optional[Dict[str, Any]] = None,
5474
5477
  data: Optional[Dict[str, Any]] = None,
5478
+ traits: Optional[Dict[str, Any]] = None,
5475
5479
  tags: Optional[List[str]] = None,
5476
5480
  status: Optional[str] = None,
5477
5481
  active: Optional[bool] = None,
@@ -5486,6 +5490,8 @@ def create_representation(
5486
5490
  files (Optional[list[dict]]): Representation files information.
5487
5491
  attrib (Optional[dict[str, Any]]): Representation attributes.
5488
5492
  data (Optional[dict[str, Any]]): Representation data.
5493
+ traits (Optional[dict[str, Any]]): Representation traits
5494
+ serialized data as dict.
5489
5495
  tags (Optional[Iterable[str]]): Representation tags.
5490
5496
  status (Optional[str]): Representation status.
5491
5497
  active (Optional[bool]): Representation active state.
@@ -5504,6 +5510,7 @@ def create_representation(
5504
5510
  files=files,
5505
5511
  attrib=attrib,
5506
5512
  data=data,
5513
+ traits=traits,
5507
5514
  tags=tags,
5508
5515
  status=status,
5509
5516
  active=active,
@@ -5519,6 +5526,7 @@ def update_representation(
5519
5526
  files: Optional[List[Dict[str, Any]]] = None,
5520
5527
  attrib: Optional[Dict[str, Any]] = None,
5521
5528
  data: Optional[Dict[str, Any]] = None,
5529
+ traits: Optional[Dict[str, Any]] = None,
5522
5530
  tags: Optional[List[str]] = None,
5523
5531
  status: Optional[str] = None,
5524
5532
  active: Optional[bool] = None,
@@ -5539,6 +5547,7 @@ def update_representation(
5539
5547
  information.
5540
5548
  attrib (Optional[dict[str, Any]]): New attributes.
5541
5549
  data (Optional[dict[str, Any]]): New data.
5550
+ traits (Optional[dict[str, Any]]): New traits.
5542
5551
  tags (Optional[Iterable[str]]): New tags.
5543
5552
  status (Optional[str]): New status.
5544
5553
  active (Optional[bool]): New active state.
@@ -5553,6 +5562,7 @@ def update_representation(
5553
5562
  files=files,
5554
5563
  attrib=attrib,
5555
5564
  data=data,
5565
+ traits=traits,
5556
5566
  tags=tags,
5557
5567
  status=status,
5558
5568
  active=active,
@@ -5698,9 +5708,10 @@ def get_thumbnail_by_id(
5698
5708
  ) -> ThumbnailContent:
5699
5709
  """Get thumbnail from server by id.
5700
5710
 
5701
- Permissions of thumbnails are related to entities so thumbnails must
5702
- be queried per entity. So an entity type and entity type is required
5703
- to be passed.
5711
+ Warnings:
5712
+ Please keep in mind that used endpoint is allowed only for admins
5713
+ and managers. Use 'get_thumbnail' with entity type and id
5714
+ to allow access for artists.
5704
5715
 
5705
5716
  Notes:
5706
5717
  It is recommended to use one of prepared entity type specific
@@ -5736,7 +5747,7 @@ def get_thumbnail(
5736
5747
  """Get thumbnail from server.
5737
5748
 
5738
5749
  Permissions of thumbnails are related to entities so thumbnails must
5739
- be queried per entity. So an entity type and entity type is required
5750
+ be queried per entity. So an entity type and entity id is required
5740
5751
  to be passed.
5741
5752
 
5742
5753
  Notes:
@@ -5794,6 +5805,28 @@ def get_folder_thumbnail(
5794
5805
  )
5795
5806
 
5796
5807
 
5808
+ def get_task_thumbnail(
5809
+ project_name: str,
5810
+ task_id: str,
5811
+ ) -> ThumbnailContent:
5812
+ """Prepared method to receive thumbnail for task entity.
5813
+
5814
+ Args:
5815
+ project_name (str): Project under which the entity is located.
5816
+ task_id (str): Folder id for which thumbnail should be returned.
5817
+
5818
+ Returns:
5819
+ ThumbnailContent: Thumbnail content wrapper. Does not have to be
5820
+ valid.
5821
+
5822
+ """
5823
+ con = get_server_api_connection()
5824
+ return con.get_task_thumbnail(
5825
+ project_name=project_name,
5826
+ task_id=task_id,
5827
+ )
5828
+
5829
+
5797
5830
  def get_version_thumbnail(
5798
5831
  project_name: str,
5799
5832
  version_id: str,
@@ -131,6 +131,7 @@ DEFAULT_REPRESENTATION_FIELDS = {
131
131
  "data",
132
132
  "status",
133
133
  "tags",
134
+ "traits",
134
135
  }
135
136
 
136
137
  REPRESENTATION_FILES_FIELDS = {
@@ -49,66 +49,22 @@ class EntityHub(object):
49
49
  Args:
50
50
  project_name (str): Name of project where changes will happen.
51
51
  connection (ServerAPI): Connection to server with logged user.
52
- allow_data_changes (bool): This option gives ability to change 'data'
53
- key on entities. This is not recommended as 'data' may be used for
54
- secure information and would also slow down server queries. Content
55
- of 'data' key can't be received only GraphQl.
56
52
 
57
53
  """
58
54
 
59
- def __init__(
60
- self, project_name, connection=None, allow_data_changes=None
61
- ):
55
+ def __init__(self, project_name, connection=None):
62
56
  if not connection:
63
57
  connection = get_server_api_connection()
64
- major, minor, _, _, _ = connection.server_version_tuple
65
- path_start_with_slash = True
66
- if (major, minor) < (0, 6):
67
- path_start_with_slash = False
68
-
69
- if allow_data_changes is None:
70
- allow_data_changes = connection.graphql_allows_data_in_query
71
58
 
72
59
  self._connection = connection
73
- self._path_start_with_slash = path_start_with_slash
74
60
 
75
61
  self._project_name = project_name
76
62
  self._entities_by_id = {}
77
63
  self._entities_by_parent_id = collections.defaultdict(list)
78
64
  self._project_entity = UNKNOWN_VALUE
79
65
 
80
- self._allow_data_changes = allow_data_changes
81
-
82
66
  self._path_reset_queue = None
83
67
 
84
- @property
85
- def allow_data_changes(self):
86
- """Entity hub allows changes of 'data' key on entities.
87
-
88
- Data are private and not all users may have access to them.
89
-
90
- Older version of AYON server allowed to get 'data' for entity only
91
- using REST api calls, which means to query each entity on-by-one
92
- from server.
93
-
94
- Returns:
95
- bool: Data changes are allowed.
96
-
97
- """
98
- return self._allow_data_changes
99
-
100
- @property
101
- def path_start_with_slash(self):
102
- """Folder path should start with slash.
103
-
104
- This changed in 0.6.x server version.
105
-
106
- Returns:
107
- bool: Path starts with slash.
108
-
109
- """
110
- return self._path_start_with_slash
111
-
112
68
  @property
113
69
  def project_name(self):
114
70
  """Project name which is maintained by hub.
@@ -898,8 +854,7 @@ class EntityHub(object):
898
854
  self._connection.get_default_fields_for_type("folder")
899
855
  )
900
856
  folder_fields.add("hasProducts")
901
- if self._allow_data_changes:
902
- folder_fields.add("data")
857
+ folder_fields.add("data")
903
858
  return folder_fields
904
859
 
905
860
  def _get_task_fields(self) -> Set[str]:
@@ -1717,10 +1672,7 @@ class BaseEntity(ABC):
1717
1672
 
1718
1673
  """
1719
1674
  changes = {}
1720
- if (
1721
- self._entity_hub.allow_data_changes
1722
- and self._data is not UNKNOWN_VALUE
1723
- ):
1675
+ if self._data is not UNKNOWN_VALUE:
1724
1676
  data_changes = self._data.get_changes()
1725
1677
  if data_changes:
1726
1678
  changes["data"] = data_changes
@@ -3149,10 +3101,8 @@ class FolderEntity(BaseEntity):
3149
3101
  if parent.entity_type == "folder":
3150
3102
  parent_path = parent.path
3151
3103
  path = "/".join([parent_path, self.name])
3152
- elif self._entity_hub.path_start_with_slash:
3153
- path = "/{}".format(self.name)
3154
3104
  else:
3155
- path = self.name
3105
+ path = "/{}".format(self.name)
3156
3106
  self._path = path
3157
3107
  return self._path
3158
3108
 
@@ -3260,10 +3210,7 @@ class FolderEntity(BaseEntity):
3260
3210
  if self.thumbnail_id is not UNKNOWN_VALUE:
3261
3211
  output["thumbnailId"] = self.thumbnail_id
3262
3212
 
3263
- if (
3264
- self._entity_hub.allow_data_changes
3265
- and self._data is not UNKNOWN_VALUE
3266
- ):
3213
+ if self._data is not UNKNOWN_VALUE:
3267
3214
  output["data"] = self._data.get_new_entity_value()
3268
3215
  return output
3269
3216
 
@@ -3451,10 +3398,7 @@ class TaskEntity(BaseEntity):
3451
3398
  if self.assignees:
3452
3399
  output["assignees"] = self.assignees
3453
3400
 
3454
- if (
3455
- self._entity_hub.allow_data_changes
3456
- and self._data is not UNKNOWN_VALUE
3457
- ):
3401
+ if self._data is not UNKNOWN_VALUE:
3458
3402
  output["data"] = self._data.get_new_entity_value()
3459
3403
  return output
3460
3404
 
@@ -3561,10 +3505,7 @@ class ProductEntity(BaseEntity):
3561
3505
  if self.tags:
3562
3506
  output["tags"] = self.tags
3563
3507
 
3564
- if (
3565
- self._entity_hub.allow_data_changes
3566
- and self._data is not UNKNOWN_VALUE
3567
- ):
3508
+ if self._data is not UNKNOWN_VALUE:
3568
3509
  output["data"] = self._data.get_new_entity_value()
3569
3510
  return output
3570
3511
 
@@ -3693,9 +3634,6 @@ class VersionEntity(BaseEntity):
3693
3634
  if self.status:
3694
3635
  output["status"] = self.status
3695
3636
 
3696
- if (
3697
- self._entity_hub.allow_data_changes
3698
- and self._data is not UNKNOWN_VALUE
3699
- ):
3637
+ if self._data is not UNKNOWN_VALUE:
3700
3638
  output["data"] = self._data.get_new_entity_value()
3701
3639
  return output
@@ -341,6 +341,7 @@ def versions_graphql_query(fields):
341
341
  project_name_var = query.add_variable("projectName", "String!")
342
342
  product_ids_var = query.add_variable("productIds", "[String!]")
343
343
  version_ids_var = query.add_variable("versionIds", "[String!]")
344
+ task_ids_var = query.add_variable("taskIds", "[String!]")
344
345
  versions_var = query.add_variable("versions", "[Int!]")
345
346
  hero_only_var = query.add_variable("heroOnly", "Boolean")
346
347
  latest_only_var = query.add_variable("latestOnly", "Boolean")
@@ -357,6 +358,7 @@ def versions_graphql_query(fields):
357
358
  versions_field.set_filter("ids", version_ids_var)
358
359
  versions_field.set_filter("productIds", product_ids_var)
359
360
  versions_field.set_filter("versions", versions_var)
361
+ versions_field.set_filter("taskIds", task_ids_var)
360
362
  versions_field.set_filter("heroOnly", hero_only_var)
361
363
  versions_field.set_filter("latestOnly", latest_only_var)
362
364
  versions_field.set_filter("heroOrLatestOnly", hero_or_latest_only_var)
@@ -617,10 +619,12 @@ def events_graphql_query(fields, order, use_states=False):
617
619
  def users_graphql_query(fields):
618
620
  query = GraphQlQuery("Users")
619
621
  names_var = query.add_variable("userNames", "[String!]")
622
+ emails_var = query.add_variable("emails", "[String!]")
620
623
  project_name_var = query.add_variable("projectName", "String!")
621
624
 
622
625
  users_field = query.add_field_with_edges("users")
623
626
  users_field.set_filter("names", names_var)
627
+ users_field.set_filter("emails", emails_var)
624
628
  users_field.set_filter("projectName", project_name_var)
625
629
 
626
630
  nested_fields = fields_to_dict(set(fields))
@@ -276,7 +276,8 @@ def new_representation_entity(
276
276
  tags=None,
277
277
  attribs=None,
278
278
  data=None,
279
- entity_id=None
279
+ traits=None,
280
+ entity_id=None,
280
281
  ):
281
282
  """Create skeleton data of representation entity.
282
283
 
@@ -290,6 +291,8 @@ def new_representation_entity(
290
291
  attribs (Optional[Dict[str, Any]]): Explicitly set attributes
291
292
  of representation.
292
293
  data (Optional[Dict[str, Any]]): Representation entity data.
294
+ traits (Optional[Dict[str, Any]]): Representation traits. Empty
295
+ if not passed.
293
296
  entity_id (Optional[str]): Predefined id of entity. New id is created
294
297
  if not passed.
295
298
 
@@ -309,8 +312,10 @@ def new_representation_entity(
309
312
  "files": files,
310
313
  "name": name,
311
314
  "data": data,
312
- "attrib": attribs
315
+ "attrib": attribs,
313
316
  }
317
+ if traits:
318
+ output["traits"] = traits
314
319
  if tags:
315
320
  output["tags"] = tags
316
321
  if status:
@@ -1188,7 +1193,10 @@ class OperationsSession(object):
1188
1193
  update_data[key] = value
1189
1194
 
1190
1195
  return self.update_entity(
1191
- project_name, "product", update_data
1196
+ project_name,
1197
+ "product",
1198
+ product_id,
1199
+ update_data
1192
1200
  )
1193
1201
 
1194
1202
  def delete_product(self, project_name, product_id):
@@ -1356,6 +1364,7 @@ class OperationsSession(object):
1356
1364
  files=None,
1357
1365
  attrib=None,
1358
1366
  data=None,
1367
+ traits=None,
1359
1368
  tags=None,
1360
1369
  status=None,
1361
1370
  active=None,
@@ -1370,6 +1379,8 @@ class OperationsSession(object):
1370
1379
  files (Optional[list[dict]]): Representation files information.
1371
1380
  attrib (Optional[dict[str, Any]]): Representation attributes.
1372
1381
  data (Optional[dict[str, Any]]): Representation data.
1382
+ traits (Optional[Dict[str, Any]]): Representation traits. Empty
1383
+ if not passed.
1373
1384
  tags (Optional[Iterable[str]]): Representation tags.
1374
1385
  status (Optional[str]): Representation status.
1375
1386
  active (Optional[bool]): Representation active state.
@@ -1391,6 +1402,7 @@ class OperationsSession(object):
1391
1402
  ("files", files),
1392
1403
  ("attrib", attrib),
1393
1404
  ("data", data),
1405
+ ("traits", traits),
1394
1406
  ("tags", tags),
1395
1407
  ("status", status),
1396
1408
  ("active", active),
@@ -1413,6 +1425,7 @@ class OperationsSession(object):
1413
1425
  files=None,
1414
1426
  attrib=None,
1415
1427
  data=None,
1428
+ traits=None,
1416
1429
  tags=None,
1417
1430
  status=None,
1418
1431
  active=None,
@@ -1433,6 +1446,7 @@ class OperationsSession(object):
1433
1446
  information.
1434
1447
  attrib (Optional[dict[str, Any]]): New attributes.
1435
1448
  data (Optional[dict[str, Any]]): New data.
1449
+ traits (Optional[Dict[str, Any]]): New representation traits.
1436
1450
  tags (Optional[Iterable[str]]): New tags.
1437
1451
  status (Optional[str]): New status.
1438
1452
  active (Optional[bool]): New active state.
@@ -1448,6 +1462,7 @@ class OperationsSession(object):
1448
1462
  ("files", files),
1449
1463
  ("attrib", attrib),
1450
1464
  ("data", data),
1465
+ ("traits", traits),
1451
1466
  ("tags", tags),
1452
1467
  ("status", status),
1453
1468
  ("active", active),
@@ -1456,7 +1471,10 @@ class OperationsSession(object):
1456
1471
  update_data[key] = value
1457
1472
 
1458
1473
  return self.update_entity(
1459
- project_name, "representation", update_data
1474
+ project_name,
1475
+ "representation",
1476
+ representation_id,
1477
+ update_data
1460
1478
  )
1461
1479
 
1462
1480
  def delete_representation(self, project_name, representation_id):
@@ -52,7 +52,6 @@ from .constants import (
52
52
  DEFAULT_EVENT_FIELDS,
53
53
  DEFAULT_ACTIVITY_FIELDS,
54
54
  DEFAULT_USER_FIELDS,
55
- DEFAULT_LINK_FIELDS,
56
55
  )
57
56
  from .graphql import GraphQlQuery, INTROSPECTION_QUERY
58
57
  from .graphql_queries import (
@@ -520,7 +519,7 @@ class ServerAPI(object):
520
519
  self._server_version = None
521
520
  self._server_version_tuple = None
522
521
 
523
- self._graphql_allows_data_in_query = None
522
+ self._graphql_allows_traits_in_representations: Optional[bool] = None
524
523
 
525
524
  self._session = None
526
525
 
@@ -1097,24 +1096,15 @@ class ServerAPI(object):
1097
1096
  )
1098
1097
 
1099
1098
  @property
1100
- def graphql_allows_data_in_query(self) -> bool:
1101
- """GraphQl query can support 'data' field.
1102
-
1103
- This applies only to project hierarchy entities 'project', 'folder',
1104
- 'task', 'product', 'version' and 'representation'. Others like 'user'
1105
- still require to use rest api to access 'data'.
1106
-
1107
- Returns:
1108
- bool: True if server supports 'data' field in GraphQl query.
1109
-
1110
- """
1111
- if self._graphql_allows_data_in_query is None:
1099
+ def graphql_allows_traits_in_representations(self) -> bool:
1100
+ """Check server support for representation traits."""
1101
+ if self._graphql_allows_traits_in_representations is None:
1112
1102
  major, minor, patch, _, _ = self.server_version_tuple
1113
- graphql_allows_data_in_query = True
1114
- if (major, minor, patch) < (0, 5, 5):
1115
- graphql_allows_data_in_query = False
1116
- self._graphql_allows_data_in_query = graphql_allows_data_in_query
1117
- return self._graphql_allows_data_in_query
1103
+ self._graphql_allows_traits_in_representations = (
1104
+ (major, minor, patch) >= (1, 7, 5)
1105
+ )
1106
+ return self._graphql_allows_traits_in_representations
1107
+
1118
1108
 
1119
1109
  def _get_user_info(self) -> Optional[Dict[str, Any]]:
1120
1110
  if self._access_token is None:
@@ -1143,6 +1133,7 @@ class ServerAPI(object):
1143
1133
  self,
1144
1134
  project_name: Optional[str] = None,
1145
1135
  usernames: Optional[Iterable[str]] = None,
1136
+ emails: Optional[Iterable[str]] = None,
1146
1137
  fields: Optional[Iterable[str]] = None,
1147
1138
  ) -> Generator[Dict[str, Any], None, None]:
1148
1139
  """Get Users.
@@ -1153,6 +1144,7 @@ class ServerAPI(object):
1153
1144
  Args:
1154
1145
  project_name (Optional[str]): Project name.
1155
1146
  usernames (Optional[Iterable[str]]): Filter by usernames.
1147
+ emails (Optional[Iterable[str]]): Filter by emails.
1156
1148
  fields (Optional[Iterable[str]]): Fields to be queried
1157
1149
  for users.
1158
1150
 
@@ -1167,6 +1159,22 @@ class ServerAPI(object):
1167
1159
  return
1168
1160
  filters["userNames"] = list(usernames)
1169
1161
 
1162
+ if emails is not None:
1163
+ emails = set(emails)
1164
+ if not emails:
1165
+ return
1166
+
1167
+ major, minor, patch, _, _ = self.server_version_tuple
1168
+ emails_filter_available = (major, minor, patch) > (1, 7, 3)
1169
+ if not emails_filter_available:
1170
+ server_version = self.get_server_version()
1171
+ raise ValueError(
1172
+ "Filtering by emails is not supported by"
1173
+ f" server version {server_version}."
1174
+ )
1175
+
1176
+ filters["emails"] = list(emails)
1177
+
1170
1178
  if project_name is not None:
1171
1179
  filters["projectName"] = project_name
1172
1180
 
@@ -1656,21 +1664,6 @@ class ServerAPI(object):
1656
1664
  )
1657
1665
  if value is not None
1658
1666
  }
1659
- # 'progress' and 'retries' are available since 0.5.x server version
1660
- major, minor, _, _, _ = self.server_version_tuple
1661
- if (major, minor) < (0, 5):
1662
- args = []
1663
- if progress is not None:
1664
- args.append("progress")
1665
- if retries is not None:
1666
- args.append("retries")
1667
- fields = ", ".join(f"'{f}'" for f in args)
1668
- ending = "s" if len(args) > 1 else ""
1669
- raise ValueError(
1670
- f"Your server version '{self.server_version}' does not"
1671
- f" support update of {fields} field{ending} on event."
1672
- " The fields are supported since server version '0.5'."
1673
- )
1674
1667
 
1675
1668
  response = self.patch(
1676
1669
  f"events/{event_id}",
@@ -2730,36 +2723,27 @@ class ServerAPI(object):
2730
2723
 
2731
2724
  if entity_type == "project":
2732
2725
  entity_type_defaults = set(DEFAULT_PROJECT_FIELDS)
2733
- if not self.graphql_allows_data_in_query:
2734
- entity_type_defaults.discard("data")
2735
2726
 
2736
2727
  elif entity_type == "folder":
2737
2728
  entity_type_defaults = set(DEFAULT_FOLDER_FIELDS)
2738
- if not self.graphql_allows_data_in_query:
2739
- entity_type_defaults.discard("data")
2740
2729
 
2741
2730
  elif entity_type == "task":
2742
2731
  entity_type_defaults = set(DEFAULT_TASK_FIELDS)
2743
- if not self.graphql_allows_data_in_query:
2744
- entity_type_defaults.discard("data")
2745
2732
 
2746
2733
  elif entity_type == "product":
2747
2734
  entity_type_defaults = set(DEFAULT_PRODUCT_FIELDS)
2748
- if not self.graphql_allows_data_in_query:
2749
- entity_type_defaults.discard("data")
2750
2735
 
2751
2736
  elif entity_type == "version":
2752
2737
  entity_type_defaults = set(DEFAULT_VERSION_FIELDS)
2753
- if not self.graphql_allows_data_in_query:
2754
- entity_type_defaults.discard("data")
2755
2738
 
2756
2739
  elif entity_type == "representation":
2757
2740
  entity_type_defaults = (
2758
2741
  DEFAULT_REPRESENTATION_FIELDS
2759
2742
  | REPRESENTATION_FILES_FIELDS
2760
2743
  )
2761
- if not self.graphql_allows_data_in_query:
2762
- entity_type_defaults.discard("data")
2744
+
2745
+ if not self.graphql_allows_traits_in_representations:
2746
+ entity_type_defaults.discard("traits")
2763
2747
 
2764
2748
  elif entity_type == "folderType":
2765
2749
  entity_type_defaults = set(DEFAULT_FOLDER_TYPE_FIELDS)
@@ -2772,8 +2756,6 @@ class ServerAPI(object):
2772
2756
 
2773
2757
  elif entity_type == "workfile":
2774
2758
  entity_type_defaults = set(DEFAULT_WORKFILE_INFO_FIELDS)
2775
- if not self.graphql_allows_data_in_query:
2776
- entity_type_defaults.discard("data")
2777
2759
 
2778
2760
  elif entity_type == "user":
2779
2761
  entity_type_defaults = set(DEFAULT_USER_FIELDS)
@@ -4646,10 +4628,14 @@ class ServerAPI(object):
4646
4628
  return
4647
4629
 
4648
4630
  self._prepare_fields("project", fields, own_attributes)
4631
+ if active is not None:
4632
+ fields.add("active")
4649
4633
 
4650
4634
  query = projects_graphql_query(fields)
4651
4635
  for parsed_data in query.continuous_query(self):
4652
4636
  for project in parsed_data["projects"]:
4637
+ if active is not None and active is not project["active"]:
4638
+ continue
4653
4639
  if own_attributes:
4654
4640
  fill_own_attribs(project)
4655
4641
  yield project
@@ -4930,15 +4916,10 @@ class ServerAPI(object):
4930
4916
  fields = set(fields)
4931
4917
  self._prepare_fields("folder", fields)
4932
4918
 
4933
- use_rest = False
4934
- if "data" in fields and not self.graphql_allows_data_in_query:
4935
- use_rest = True
4936
- fields = {"id"}
4937
-
4938
4919
  if active is not None:
4939
4920
  fields.add("active")
4940
4921
 
4941
- if own_attributes and not use_rest:
4922
+ if own_attributes:
4942
4923
  fields.add("ownAttrib")
4943
4924
 
4944
4925
  query = folders_graphql_query(fields)
@@ -4950,10 +4931,7 @@ class ServerAPI(object):
4950
4931
  if active is not None and active is not folder["active"]:
4951
4932
  continue
4952
4933
 
4953
- if use_rest:
4954
- folder = self.get_rest_folder(project_name, folder["id"])
4955
- else:
4956
- self._convert_entity_data(folder)
4934
+ self._convert_entity_data(folder)
4957
4935
 
4958
4936
  if own_attributes:
4959
4937
  fill_own_attribs(folder)
@@ -5325,11 +5303,6 @@ class ServerAPI(object):
5325
5303
  fields = set(fields)
5326
5304
  self._prepare_fields("task", fields, own_attributes)
5327
5305
 
5328
- use_rest = False
5329
- if "data" in fields and not self.graphql_allows_data_in_query:
5330
- use_rest = True
5331
- fields = {"id"}
5332
-
5333
5306
  if active is not None:
5334
5307
  fields.add("active")
5335
5308
 
@@ -5342,10 +5315,7 @@ class ServerAPI(object):
5342
5315
  if active is not None and active is not task["active"]:
5343
5316
  continue
5344
5317
 
5345
- if use_rest:
5346
- task = self.get_rest_task(project_name, task["id"])
5347
- else:
5348
- self._convert_entity_data(task)
5318
+ self._convert_entity_data(task)
5349
5319
 
5350
5320
  if own_attributes:
5351
5321
  fill_own_attribs(task)
@@ -5487,11 +5457,6 @@ class ServerAPI(object):
5487
5457
  fields = set(fields)
5488
5458
  self._prepare_fields("task", fields, own_attributes)
5489
5459
 
5490
- use_rest = False
5491
- if "data" in fields and not self.graphql_allows_data_in_query:
5492
- use_rest = True
5493
- fields = {"id"}
5494
-
5495
5460
  if active is not None:
5496
5461
  fields.add("active")
5497
5462
 
@@ -5510,10 +5475,7 @@ class ServerAPI(object):
5510
5475
  if active is not None and active is not task["active"]:
5511
5476
  continue
5512
5477
 
5513
- if use_rest:
5514
- task = self.get_rest_task(project_name, task["id"])
5515
- else:
5516
- self._convert_entity_data(task)
5478
+ self._convert_entity_data(task)
5517
5479
 
5518
5480
  if own_attributes:
5519
5481
  fill_own_attribs(task)
@@ -5763,15 +5725,11 @@ class ServerAPI(object):
5763
5725
  project_name: str,
5764
5726
  product: "ProductDict",
5765
5727
  active: "Union[bool, None]",
5766
- use_rest: bool,
5767
5728
  ) -> Optional["ProductDict"]:
5768
5729
  if active is not None and product["active"] is not active:
5769
5730
  return None
5770
5731
 
5771
- if use_rest:
5772
- product = self.get_rest_product(project_name, product["id"])
5773
- else:
5774
- self._convert_entity_data(product)
5732
+ self._convert_entity_data(product)
5775
5733
 
5776
5734
  return product
5777
5735
 
@@ -5864,11 +5822,6 @@ class ServerAPI(object):
5864
5822
  else:
5865
5823
  fields = self.get_default_fields_for_type("product")
5866
5824
 
5867
- use_rest = False
5868
- if "data" in fields and not self.graphql_allows_data_in_query:
5869
- use_rest = True
5870
- fields = {"id"}
5871
-
5872
5825
  if active is not None:
5873
5826
  fields.add("active")
5874
5827
 
@@ -5926,7 +5879,7 @@ class ServerAPI(object):
5926
5879
  products_by_folder_id = collections.defaultdict(list)
5927
5880
  for product in products:
5928
5881
  filtered_product = self._filter_product(
5929
- project_name, product, active, use_rest
5882
+ project_name, product, active
5930
5883
  )
5931
5884
  if filtered_product is not None:
5932
5885
  folder_id = filtered_product["folderId"]
@@ -5940,7 +5893,7 @@ class ServerAPI(object):
5940
5893
  else:
5941
5894
  for product in products:
5942
5895
  filtered_product = self._filter_product(
5943
- project_name, product, active, use_rest
5896
+ project_name, product, active
5944
5897
  )
5945
5898
  if filtered_product is not None:
5946
5899
  yield filtered_product
@@ -6287,11 +6240,6 @@ class ServerAPI(object):
6287
6240
  # Make sure fields have minimum required fields
6288
6241
  fields |= {"id", "version"}
6289
6242
 
6290
- use_rest = False
6291
- if "data" in fields and not self.graphql_allows_data_in_query:
6292
- use_rest = True
6293
- fields = {"id"}
6294
-
6295
6243
  if active is not None:
6296
6244
  fields.add("active")
6297
6245
 
@@ -6364,12 +6312,7 @@ class ServerAPI(object):
6364
6312
  if not hero and version["version"] < 0:
6365
6313
  continue
6366
6314
 
6367
- if use_rest:
6368
- version = self.get_rest_version(
6369
- project_name, version["id"]
6370
- )
6371
- else:
6372
- self._convert_entity_data(version)
6315
+ self._convert_entity_data(version)
6373
6316
 
6374
6317
  yield version
6375
6318
 
@@ -6931,11 +6874,6 @@ class ServerAPI(object):
6931
6874
  fields = set(fields)
6932
6875
  self._prepare_fields("representation", fields)
6933
6876
 
6934
- use_rest = False
6935
- if "data" in fields and not self.graphql_allows_data_in_query:
6936
- use_rest = True
6937
- fields = {"id"}
6938
-
6939
6877
  if active is not None:
6940
6878
  fields.add("active")
6941
6879
 
@@ -7017,12 +6955,7 @@ class ServerAPI(object):
7017
6955
  if active is not None and active is not repre["active"]:
7018
6956
  continue
7019
6957
 
7020
- if use_rest:
7021
- repre = self.get_rest_representation(
7022
- project_name, repre["id"]
7023
- )
7024
- else:
7025
- self._convert_entity_data(repre)
6958
+ self._convert_entity_data(repre)
7026
6959
 
7027
6960
  self._representation_conversion(repre)
7028
6961
 
@@ -7452,6 +7385,7 @@ class ServerAPI(object):
7452
7385
  files: Optional[List[Dict[str, Any]]] = None,
7453
7386
  attrib: Optional[Dict[str, Any]] = None,
7454
7387
  data: Optional[Dict[str, Any]] = None,
7388
+ traits: Optional[Dict[str, Any]] = None,
7455
7389
  tags: Optional[List[str]]=None,
7456
7390
  status: Optional[str] = None,
7457
7391
  active: Optional[bool] = None,
@@ -7466,6 +7400,8 @@ class ServerAPI(object):
7466
7400
  files (Optional[list[dict]]): Representation files information.
7467
7401
  attrib (Optional[dict[str, Any]]): Representation attributes.
7468
7402
  data (Optional[dict[str, Any]]): Representation data.
7403
+ traits (Optional[dict[str, Any]]): Representation traits
7404
+ serialized data as dict.
7469
7405
  tags (Optional[Iterable[str]]): Representation tags.
7470
7406
  status (Optional[str]): Representation status.
7471
7407
  active (Optional[bool]): Representation active state.
@@ -7487,6 +7423,7 @@ class ServerAPI(object):
7487
7423
  ("files", files),
7488
7424
  ("attrib", attrib),
7489
7425
  ("data", data),
7426
+ ("traits", traits),
7490
7427
  ("tags", tags),
7491
7428
  ("status", status),
7492
7429
  ("active", active),
@@ -7510,6 +7447,7 @@ class ServerAPI(object):
7510
7447
  files: Optional[List[Dict[str, Any]]] = None,
7511
7448
  attrib: Optional[Dict[str, Any]] = None,
7512
7449
  data: Optional[Dict[str, Any]] = None,
7450
+ traits: Optional[Dict[str, Any]] = None,
7513
7451
  tags: Optional[List[str]] = None,
7514
7452
  status: Optional[str] = None,
7515
7453
  active: Optional[bool] = None,
@@ -7530,6 +7468,7 @@ class ServerAPI(object):
7530
7468
  information.
7531
7469
  attrib (Optional[dict[str, Any]]): New attributes.
7532
7470
  data (Optional[dict[str, Any]]): New data.
7471
+ traits (Optional[dict[str, Any]]): New traits.
7533
7472
  tags (Optional[Iterable[str]]): New tags.
7534
7473
  status (Optional[str]): New status.
7535
7474
  active (Optional[bool]): New active state.
@@ -7542,6 +7481,7 @@ class ServerAPI(object):
7542
7481
  ("files", files),
7543
7482
  ("attrib", attrib),
7544
7483
  ("data", data),
7484
+ ("traits", traits),
7545
7485
  ("tags", tags),
7546
7486
  ("status", status),
7547
7487
  ("active", active),
@@ -7765,9 +7705,10 @@ class ServerAPI(object):
7765
7705
  ) -> ThumbnailContent:
7766
7706
  """Get thumbnail from server by id.
7767
7707
 
7768
- Permissions of thumbnails are related to entities so thumbnails must
7769
- be queried per entity. So an entity type and entity type is required
7770
- to be passed.
7708
+ Warnings:
7709
+ Please keep in mind that used endpoint is allowed only for admins
7710
+ and managers. Use 'get_thumbnail' with entity type and id
7711
+ to allow access for artists.
7771
7712
 
7772
7713
  Notes:
7773
7714
  It is recommended to use one of prepared entity type specific
@@ -7802,7 +7743,7 @@ class ServerAPI(object):
7802
7743
  """Get thumbnail from server.
7803
7744
 
7804
7745
  Permissions of thumbnails are related to entities so thumbnails must
7805
- be queried per entity. So an entity type and entity type is required
7746
+ be queried per entity. So an entity type and entity id is required
7806
7747
  to be passed.
7807
7748
 
7808
7749
  Notes:
@@ -7826,10 +7767,17 @@ class ServerAPI(object):
7826
7767
 
7827
7768
  """
7828
7769
  if thumbnail_id:
7829
- return self.get_thumbnail_by_id(project_name, thumbnail_id)
7770
+ warnings.warn(
7771
+ (
7772
+ "Function 'get_thumbnail' got 'thumbnail_id' which"
7773
+ " is deprecated and will be removed in future version."
7774
+ ),
7775
+ DeprecationWarning
7776
+ )
7830
7777
 
7831
7778
  if entity_type in (
7832
7779
  "folder",
7780
+ "task",
7833
7781
  "version",
7834
7782
  "workfile",
7835
7783
  ):
@@ -7859,10 +7807,36 @@ class ServerAPI(object):
7859
7807
  valid.
7860
7808
 
7861
7809
  """
7810
+ if thumbnail_id:
7811
+ warnings.warn(
7812
+ (
7813
+ "Function 'get_folder_thumbnail' got 'thumbnail_id' which"
7814
+ " is deprecated and will be removed in future version."
7815
+ ),
7816
+ DeprecationWarning
7817
+ )
7862
7818
  return self.get_thumbnail(
7863
- project_name, "folder", folder_id, thumbnail_id
7819
+ project_name, "folder", folder_id
7864
7820
  )
7865
7821
 
7822
+ def get_task_thumbnail(
7823
+ self,
7824
+ project_name: str,
7825
+ task_id: str,
7826
+ ) -> ThumbnailContent:
7827
+ """Prepared method to receive thumbnail for task entity.
7828
+
7829
+ Args:
7830
+ project_name (str): Project under which the entity is located.
7831
+ task_id (str): Folder id for which thumbnail should be returned.
7832
+
7833
+ Returns:
7834
+ ThumbnailContent: Thumbnail content wrapper. Does not have to be
7835
+ valid.
7836
+
7837
+ """
7838
+ return self.get_thumbnail(project_name, "task", task_id)
7839
+
7866
7840
  def get_version_thumbnail(
7867
7841
  self,
7868
7842
  project_name: str,
@@ -7883,8 +7857,16 @@ class ServerAPI(object):
7883
7857
  valid.
7884
7858
 
7885
7859
  """
7860
+ if thumbnail_id:
7861
+ warnings.warn(
7862
+ (
7863
+ "Function 'get_version_thumbnail' got 'thumbnail_id' which"
7864
+ " is deprecated and will be removed in future version."
7865
+ ),
7866
+ DeprecationWarning
7867
+ )
7886
7868
  return self.get_thumbnail(
7887
- project_name, "version", version_id, thumbnail_id
7869
+ project_name, "version", version_id
7888
7870
  )
7889
7871
 
7890
7872
  def get_workfile_thumbnail(
@@ -7907,8 +7889,17 @@ class ServerAPI(object):
7907
7889
  valid.
7908
7890
 
7909
7891
  """
7892
+ if thumbnail_id:
7893
+ warnings.warn(
7894
+ (
7895
+ "Function 'get_workfile_thumbnail' got 'thumbnail_id'"
7896
+ " which is deprecated and will be removed in future"
7897
+ " version."
7898
+ ),
7899
+ DeprecationWarning
7900
+ )
7910
7901
  return self.get_thumbnail(
7911
- project_name, "workfile", workfile_id, thumbnail_id
7902
+ project_name, "workfile", workfile_id
7912
7903
  )
7913
7904
 
7914
7905
  def create_thumbnail(
@@ -8345,22 +8336,8 @@ class ServerAPI(object):
8345
8336
  kwargs = {
8346
8337
  "input": input_id,
8347
8338
  "output": output_id,
8339
+ "linkType": full_link_type_name,
8348
8340
  }
8349
- major, minor, patch, rel, _ = self.server_version_tuple
8350
- rel_regex = re.compile(r"rc\.[0-5]")
8351
- if (
8352
- ((major, minor, patch) == (1, 0, 0) and rel_regex.match(rel))
8353
- or (major, minor, patch) < (1, 0, 0)
8354
- ):
8355
- kwargs["link"] = full_link_type_name
8356
- if link_name:
8357
- raise UnsupportedServerVersion(
8358
- "Link name is not supported"
8359
- f" for version of AYON server {self.server_version}"
8360
- )
8361
- else:
8362
- kwargs["linkType"] = full_link_type_name
8363
-
8364
8341
  if link_name:
8365
8342
  kwargs["name"] = link_name
8366
8343
 
@@ -8522,23 +8499,6 @@ class ServerAPI(object):
8522
8499
  return output
8523
8500
 
8524
8501
  link_fields = {"id", "links"}
8525
- # Backwards compatibility for server version 1.0.0-rc.5 and lower
8526
- # ---------
8527
- major, minor, patch, rel, _ = self.server_version_tuple
8528
- rel_regex = re.compile(r"rc\.[0-5]")
8529
- if (
8530
- ((major, minor, patch) == (1, 0, 0) and rel_regex.match(rel))
8531
- or (major, minor, patch) < (1, 0, 0)
8532
- ):
8533
- fields = set(DEFAULT_LINK_FIELDS)
8534
- fields.discard("name")
8535
- link_fields.discard("links")
8536
- link_fields |= {
8537
- f"links.{field}"
8538
- for field in fields
8539
- }
8540
- # ---------
8541
-
8542
8502
  query = query_func(link_fields)
8543
8503
  for attr, filter_value in filters.items():
8544
8504
  query.set_variable_value(attr, filter_value)
@@ -855,11 +855,13 @@ def _get_media_mime_type_for_content_base(content: bytes) -> Optional[str]:
855
855
  if content[0:4] == b"\211PNG":
856
856
  return "image/png"
857
857
 
858
- # JPEG, JFIF or Exif
859
- if (
860
- content[0:4] == b"\xff\xd8\xff\xdb"
861
- or content[6:10] in (b"JFIF", b"Exif")
862
- ):
858
+ # JPEG
859
+ # - [0:2] is constant b"\xff\xd8"
860
+ # (ref. https://www.file-recovery.com/jpg-signature-format.htm)
861
+ # - [2:4] Marker identifier b"\xff{?}"
862
+ # (ref. https://www.disktuna.com/list-of-jpeg-markers/)
863
+ # NOTE: File ends with b"\xff\xd9"
864
+ if content[0:3] == b"\xff\xd8\xff":
863
865
  return "image/jpeg"
864
866
 
865
867
  # Webp
@@ -1,2 +1,2 @@
1
1
  """Package declaring Python API for AYON server."""
2
- __version__ = "1.0.12"
2
+ __version__ = "1.1.0"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: ayon-python-api
3
- Version: 1.0.12
3
+ Version: 1.1.0
4
4
  Summary: AYON Python API
5
5
  Home-page: https://github.com/ynput/ayon-python-api
6
6
  Author: ynput.io
@@ -1,3 +1,2 @@
1
1
  requests>=2.27.1
2
2
  Unidecode>=1.3.0
3
- appdirs<2,>=1
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "ayon-python-api"
3
- version = "1.0.12"
3
+ version = "1.1.0"
4
4
  description = "AYON Python API"
5
5
  license = {file = "LICENSE"}
6
6
  readme = {file = "README.md", content-type = "text/markdown"}
@@ -16,7 +16,6 @@ classifiers = [
16
16
  dependencies = [
17
17
  "requests >= 2.27.1",
18
18
  "Unidecode >= 1.3.0",
19
- "appdirs >=1, <2",
20
19
  ]
21
20
 
22
21
  [project.urls]
@@ -29,7 +28,7 @@ build-backend = "poetry.core.masonry.api"
29
28
 
30
29
  [tool.poetry]
31
30
  name = "ayon-python-api"
32
- version = "1.0.12"
31
+ version = "1.1.0"
33
32
  description = "AYON Python API"
34
33
  authors = [
35
34
  "ynput.io <info@ynput.io>"
@@ -42,7 +41,6 @@ packages = [
42
41
  python = ">=3.6.5"
43
42
  requests = "^2.27"
44
43
  Unidecode = "^1.3"
45
- appdirs = "^1.4"
46
44
 
47
45
  [tool.poetry.group.dev.dependencies]
48
46
  sphinx = "*"