Ayon-Python-Api 1.0.12__tar.gz → 1.1.1__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.1}/PKG-INFO +1 -1
  2. {ayon-python-api-1.0.12 → ayon-python-api-1.1.1}/ayon_api/__init__.py +2 -0
  3. {ayon-python-api-1.0.12 → ayon-python-api-1.1.1}/ayon_api/_api.py +37 -4
  4. {ayon-python-api-1.0.12 → ayon-python-api-1.1.1}/ayon_api/constants.py +1 -0
  5. {ayon-python-api-1.0.12 → ayon-python-api-1.1.1}/ayon_api/entity_hub.py +8 -70
  6. {ayon-python-api-1.0.12 → ayon-python-api-1.1.1}/ayon_api/graphql_queries.py +4 -0
  7. {ayon-python-api-1.0.12 → ayon-python-api-1.1.1}/ayon_api/operations.py +22 -4
  8. {ayon-python-api-1.0.12 → ayon-python-api-1.1.1}/ayon_api/server_api.py +111 -150
  9. {ayon-python-api-1.0.12 → ayon-python-api-1.1.1}/ayon_api/utils.py +55 -16
  10. {ayon-python-api-1.0.12 → ayon-python-api-1.1.1}/ayon_api/version.py +1 -1
  11. {ayon-python-api-1.0.12 → ayon-python-api-1.1.1}/ayon_python_api.egg-info/PKG-INFO +1 -1
  12. {ayon-python-api-1.0.12 → ayon-python-api-1.1.1}/ayon_python_api.egg-info/requires.txt +0 -1
  13. {ayon-python-api-1.0.12 → ayon-python-api-1.1.1}/pyproject.toml +2 -4
  14. {ayon-python-api-1.0.12 → ayon-python-api-1.1.1}/LICENSE +0 -0
  15. {ayon-python-api-1.0.12 → ayon-python-api-1.1.1}/README.md +0 -0
  16. {ayon-python-api-1.0.12 → ayon-python-api-1.1.1}/ayon_api/events.py +0 -0
  17. {ayon-python-api-1.0.12 → ayon-python-api-1.1.1}/ayon_api/exceptions.py +0 -0
  18. {ayon-python-api-1.0.12 → ayon-python-api-1.1.1}/ayon_api/graphql.py +0 -0
  19. {ayon-python-api-1.0.12 → ayon-python-api-1.1.1}/ayon_api/typing.py +0 -0
  20. {ayon-python-api-1.0.12 → ayon-python-api-1.1.1}/ayon_python_api.egg-info/SOURCES.txt +0 -0
  21. {ayon-python-api-1.0.12 → ayon-python-api-1.1.1}/ayon_python_api.egg-info/dependency_links.txt +0 -0
  22. {ayon-python-api-1.0.12 → ayon-python-api-1.1.1}/ayon_python_api.egg-info/top_level.txt +0 -0
  23. {ayon-python-api-1.0.12 → ayon-python-api-1.1.1}/setup.cfg +0 -0
  24. {ayon-python-api-1.0.12 → ayon-python-api-1.1.1}/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.1
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
 
@@ -1249,6 +1257,7 @@ class ServerAPI(object):
1249
1257
  "Content-Type": content_type,
1250
1258
  "x-ayon-platform": platform.system().lower(),
1251
1259
  "x-ayon-hostname": platform.node(),
1260
+ "referer": self.get_base_url(),
1252
1261
  }
1253
1262
  if self._site_id is not None:
1254
1263
  headers["x-ayon-site-id"] = self._site_id
@@ -1656,21 +1665,6 @@ class ServerAPI(object):
1656
1665
  )
1657
1666
  if value is not None
1658
1667
  }
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
1668
 
1675
1669
  response = self.patch(
1676
1670
  f"events/{event_id}",
@@ -2730,36 +2724,27 @@ class ServerAPI(object):
2730
2724
 
2731
2725
  if entity_type == "project":
2732
2726
  entity_type_defaults = set(DEFAULT_PROJECT_FIELDS)
2733
- if not self.graphql_allows_data_in_query:
2734
- entity_type_defaults.discard("data")
2735
2727
 
2736
2728
  elif entity_type == "folder":
2737
2729
  entity_type_defaults = set(DEFAULT_FOLDER_FIELDS)
2738
- if not self.graphql_allows_data_in_query:
2739
- entity_type_defaults.discard("data")
2740
2730
 
2741
2731
  elif entity_type == "task":
2742
2732
  entity_type_defaults = set(DEFAULT_TASK_FIELDS)
2743
- if not self.graphql_allows_data_in_query:
2744
- entity_type_defaults.discard("data")
2745
2733
 
2746
2734
  elif entity_type == "product":
2747
2735
  entity_type_defaults = set(DEFAULT_PRODUCT_FIELDS)
2748
- if not self.graphql_allows_data_in_query:
2749
- entity_type_defaults.discard("data")
2750
2736
 
2751
2737
  elif entity_type == "version":
2752
2738
  entity_type_defaults = set(DEFAULT_VERSION_FIELDS)
2753
- if not self.graphql_allows_data_in_query:
2754
- entity_type_defaults.discard("data")
2755
2739
 
2756
2740
  elif entity_type == "representation":
2757
2741
  entity_type_defaults = (
2758
2742
  DEFAULT_REPRESENTATION_FIELDS
2759
2743
  | REPRESENTATION_FILES_FIELDS
2760
2744
  )
2761
- if not self.graphql_allows_data_in_query:
2762
- entity_type_defaults.discard("data")
2745
+
2746
+ if not self.graphql_allows_traits_in_representations:
2747
+ entity_type_defaults.discard("traits")
2763
2748
 
2764
2749
  elif entity_type == "folderType":
2765
2750
  entity_type_defaults = set(DEFAULT_FOLDER_TYPE_FIELDS)
@@ -2772,8 +2757,6 @@ class ServerAPI(object):
2772
2757
 
2773
2758
  elif entity_type == "workfile":
2774
2759
  entity_type_defaults = set(DEFAULT_WORKFILE_INFO_FIELDS)
2775
- if not self.graphql_allows_data_in_query:
2776
- entity_type_defaults.discard("data")
2777
2760
 
2778
2761
  elif entity_type == "user":
2779
2762
  entity_type_defaults = set(DEFAULT_USER_FIELDS)
@@ -4646,10 +4629,14 @@ class ServerAPI(object):
4646
4629
  return
4647
4630
 
4648
4631
  self._prepare_fields("project", fields, own_attributes)
4632
+ if active is not None:
4633
+ fields.add("active")
4649
4634
 
4650
4635
  query = projects_graphql_query(fields)
4651
4636
  for parsed_data in query.continuous_query(self):
4652
4637
  for project in parsed_data["projects"]:
4638
+ if active is not None and active is not project["active"]:
4639
+ continue
4653
4640
  if own_attributes:
4654
4641
  fill_own_attribs(project)
4655
4642
  yield project
@@ -4930,15 +4917,10 @@ class ServerAPI(object):
4930
4917
  fields = set(fields)
4931
4918
  self._prepare_fields("folder", fields)
4932
4919
 
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
4920
  if active is not None:
4939
4921
  fields.add("active")
4940
4922
 
4941
- if own_attributes and not use_rest:
4923
+ if own_attributes:
4942
4924
  fields.add("ownAttrib")
4943
4925
 
4944
4926
  query = folders_graphql_query(fields)
@@ -4950,10 +4932,7 @@ class ServerAPI(object):
4950
4932
  if active is not None and active is not folder["active"]:
4951
4933
  continue
4952
4934
 
4953
- if use_rest:
4954
- folder = self.get_rest_folder(project_name, folder["id"])
4955
- else:
4956
- self._convert_entity_data(folder)
4935
+ self._convert_entity_data(folder)
4957
4936
 
4958
4937
  if own_attributes:
4959
4938
  fill_own_attribs(folder)
@@ -5325,11 +5304,6 @@ class ServerAPI(object):
5325
5304
  fields = set(fields)
5326
5305
  self._prepare_fields("task", fields, own_attributes)
5327
5306
 
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
5307
  if active is not None:
5334
5308
  fields.add("active")
5335
5309
 
@@ -5342,10 +5316,7 @@ class ServerAPI(object):
5342
5316
  if active is not None and active is not task["active"]:
5343
5317
  continue
5344
5318
 
5345
- if use_rest:
5346
- task = self.get_rest_task(project_name, task["id"])
5347
- else:
5348
- self._convert_entity_data(task)
5319
+ self._convert_entity_data(task)
5349
5320
 
5350
5321
  if own_attributes:
5351
5322
  fill_own_attribs(task)
@@ -5487,11 +5458,6 @@ class ServerAPI(object):
5487
5458
  fields = set(fields)
5488
5459
  self._prepare_fields("task", fields, own_attributes)
5489
5460
 
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
5461
  if active is not None:
5496
5462
  fields.add("active")
5497
5463
 
@@ -5510,10 +5476,7 @@ class ServerAPI(object):
5510
5476
  if active is not None and active is not task["active"]:
5511
5477
  continue
5512
5478
 
5513
- if use_rest:
5514
- task = self.get_rest_task(project_name, task["id"])
5515
- else:
5516
- self._convert_entity_data(task)
5479
+ self._convert_entity_data(task)
5517
5480
 
5518
5481
  if own_attributes:
5519
5482
  fill_own_attribs(task)
@@ -5763,15 +5726,11 @@ class ServerAPI(object):
5763
5726
  project_name: str,
5764
5727
  product: "ProductDict",
5765
5728
  active: "Union[bool, None]",
5766
- use_rest: bool,
5767
5729
  ) -> Optional["ProductDict"]:
5768
5730
  if active is not None and product["active"] is not active:
5769
5731
  return None
5770
5732
 
5771
- if use_rest:
5772
- product = self.get_rest_product(project_name, product["id"])
5773
- else:
5774
- self._convert_entity_data(product)
5733
+ self._convert_entity_data(product)
5775
5734
 
5776
5735
  return product
5777
5736
 
@@ -5864,11 +5823,6 @@ class ServerAPI(object):
5864
5823
  else:
5865
5824
  fields = self.get_default_fields_for_type("product")
5866
5825
 
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
5826
  if active is not None:
5873
5827
  fields.add("active")
5874
5828
 
@@ -5926,7 +5880,7 @@ class ServerAPI(object):
5926
5880
  products_by_folder_id = collections.defaultdict(list)
5927
5881
  for product in products:
5928
5882
  filtered_product = self._filter_product(
5929
- project_name, product, active, use_rest
5883
+ project_name, product, active
5930
5884
  )
5931
5885
  if filtered_product is not None:
5932
5886
  folder_id = filtered_product["folderId"]
@@ -5940,7 +5894,7 @@ class ServerAPI(object):
5940
5894
  else:
5941
5895
  for product in products:
5942
5896
  filtered_product = self._filter_product(
5943
- project_name, product, active, use_rest
5897
+ project_name, product, active
5944
5898
  )
5945
5899
  if filtered_product is not None:
5946
5900
  yield filtered_product
@@ -6287,11 +6241,6 @@ class ServerAPI(object):
6287
6241
  # Make sure fields have minimum required fields
6288
6242
  fields |= {"id", "version"}
6289
6243
 
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
6244
  if active is not None:
6296
6245
  fields.add("active")
6297
6246
 
@@ -6364,12 +6313,7 @@ class ServerAPI(object):
6364
6313
  if not hero and version["version"] < 0:
6365
6314
  continue
6366
6315
 
6367
- if use_rest:
6368
- version = self.get_rest_version(
6369
- project_name, version["id"]
6370
- )
6371
- else:
6372
- self._convert_entity_data(version)
6316
+ self._convert_entity_data(version)
6373
6317
 
6374
6318
  yield version
6375
6319
 
@@ -6931,11 +6875,6 @@ class ServerAPI(object):
6931
6875
  fields = set(fields)
6932
6876
  self._prepare_fields("representation", fields)
6933
6877
 
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
6878
  if active is not None:
6940
6879
  fields.add("active")
6941
6880
 
@@ -7017,12 +6956,7 @@ class ServerAPI(object):
7017
6956
  if active is not None and active is not repre["active"]:
7018
6957
  continue
7019
6958
 
7020
- if use_rest:
7021
- repre = self.get_rest_representation(
7022
- project_name, repre["id"]
7023
- )
7024
- else:
7025
- self._convert_entity_data(repre)
6959
+ self._convert_entity_data(repre)
7026
6960
 
7027
6961
  self._representation_conversion(repre)
7028
6962
 
@@ -7452,6 +7386,7 @@ class ServerAPI(object):
7452
7386
  files: Optional[List[Dict[str, Any]]] = None,
7453
7387
  attrib: Optional[Dict[str, Any]] = None,
7454
7388
  data: Optional[Dict[str, Any]] = None,
7389
+ traits: Optional[Dict[str, Any]] = None,
7455
7390
  tags: Optional[List[str]]=None,
7456
7391
  status: Optional[str] = None,
7457
7392
  active: Optional[bool] = None,
@@ -7466,6 +7401,8 @@ class ServerAPI(object):
7466
7401
  files (Optional[list[dict]]): Representation files information.
7467
7402
  attrib (Optional[dict[str, Any]]): Representation attributes.
7468
7403
  data (Optional[dict[str, Any]]): Representation data.
7404
+ traits (Optional[dict[str, Any]]): Representation traits
7405
+ serialized data as dict.
7469
7406
  tags (Optional[Iterable[str]]): Representation tags.
7470
7407
  status (Optional[str]): Representation status.
7471
7408
  active (Optional[bool]): Representation active state.
@@ -7487,6 +7424,7 @@ class ServerAPI(object):
7487
7424
  ("files", files),
7488
7425
  ("attrib", attrib),
7489
7426
  ("data", data),
7427
+ ("traits", traits),
7490
7428
  ("tags", tags),
7491
7429
  ("status", status),
7492
7430
  ("active", active),
@@ -7510,6 +7448,7 @@ class ServerAPI(object):
7510
7448
  files: Optional[List[Dict[str, Any]]] = None,
7511
7449
  attrib: Optional[Dict[str, Any]] = None,
7512
7450
  data: Optional[Dict[str, Any]] = None,
7451
+ traits: Optional[Dict[str, Any]] = None,
7513
7452
  tags: Optional[List[str]] = None,
7514
7453
  status: Optional[str] = None,
7515
7454
  active: Optional[bool] = None,
@@ -7530,6 +7469,7 @@ class ServerAPI(object):
7530
7469
  information.
7531
7470
  attrib (Optional[dict[str, Any]]): New attributes.
7532
7471
  data (Optional[dict[str, Any]]): New data.
7472
+ traits (Optional[dict[str, Any]]): New traits.
7533
7473
  tags (Optional[Iterable[str]]): New tags.
7534
7474
  status (Optional[str]): New status.
7535
7475
  active (Optional[bool]): New active state.
@@ -7542,6 +7482,7 @@ class ServerAPI(object):
7542
7482
  ("files", files),
7543
7483
  ("attrib", attrib),
7544
7484
  ("data", data),
7485
+ ("traits", traits),
7545
7486
  ("tags", tags),
7546
7487
  ("status", status),
7547
7488
  ("active", active),
@@ -7765,9 +7706,10 @@ class ServerAPI(object):
7765
7706
  ) -> ThumbnailContent:
7766
7707
  """Get thumbnail from server by id.
7767
7708
 
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.
7709
+ Warnings:
7710
+ Please keep in mind that used endpoint is allowed only for admins
7711
+ and managers. Use 'get_thumbnail' with entity type and id
7712
+ to allow access for artists.
7771
7713
 
7772
7714
  Notes:
7773
7715
  It is recommended to use one of prepared entity type specific
@@ -7802,7 +7744,7 @@ class ServerAPI(object):
7802
7744
  """Get thumbnail from server.
7803
7745
 
7804
7746
  Permissions of thumbnails are related to entities so thumbnails must
7805
- be queried per entity. So an entity type and entity type is required
7747
+ be queried per entity. So an entity type and entity id is required
7806
7748
  to be passed.
7807
7749
 
7808
7750
  Notes:
@@ -7826,10 +7768,17 @@ class ServerAPI(object):
7826
7768
 
7827
7769
  """
7828
7770
  if thumbnail_id:
7829
- return self.get_thumbnail_by_id(project_name, thumbnail_id)
7771
+ warnings.warn(
7772
+ (
7773
+ "Function 'get_thumbnail' got 'thumbnail_id' which"
7774
+ " is deprecated and will be removed in future version."
7775
+ ),
7776
+ DeprecationWarning
7777
+ )
7830
7778
 
7831
7779
  if entity_type in (
7832
7780
  "folder",
7781
+ "task",
7833
7782
  "version",
7834
7783
  "workfile",
7835
7784
  ):
@@ -7859,10 +7808,36 @@ class ServerAPI(object):
7859
7808
  valid.
7860
7809
 
7861
7810
  """
7811
+ if thumbnail_id:
7812
+ warnings.warn(
7813
+ (
7814
+ "Function 'get_folder_thumbnail' got 'thumbnail_id' which"
7815
+ " is deprecated and will be removed in future version."
7816
+ ),
7817
+ DeprecationWarning
7818
+ )
7862
7819
  return self.get_thumbnail(
7863
- project_name, "folder", folder_id, thumbnail_id
7820
+ project_name, "folder", folder_id
7864
7821
  )
7865
7822
 
7823
+ def get_task_thumbnail(
7824
+ self,
7825
+ project_name: str,
7826
+ task_id: str,
7827
+ ) -> ThumbnailContent:
7828
+ """Prepared method to receive thumbnail for task entity.
7829
+
7830
+ Args:
7831
+ project_name (str): Project under which the entity is located.
7832
+ task_id (str): Folder id for which thumbnail should be returned.
7833
+
7834
+ Returns:
7835
+ ThumbnailContent: Thumbnail content wrapper. Does not have to be
7836
+ valid.
7837
+
7838
+ """
7839
+ return self.get_thumbnail(project_name, "task", task_id)
7840
+
7866
7841
  def get_version_thumbnail(
7867
7842
  self,
7868
7843
  project_name: str,
@@ -7883,8 +7858,16 @@ class ServerAPI(object):
7883
7858
  valid.
7884
7859
 
7885
7860
  """
7861
+ if thumbnail_id:
7862
+ warnings.warn(
7863
+ (
7864
+ "Function 'get_version_thumbnail' got 'thumbnail_id' which"
7865
+ " is deprecated and will be removed in future version."
7866
+ ),
7867
+ DeprecationWarning
7868
+ )
7886
7869
  return self.get_thumbnail(
7887
- project_name, "version", version_id, thumbnail_id
7870
+ project_name, "version", version_id
7888
7871
  )
7889
7872
 
7890
7873
  def get_workfile_thumbnail(
@@ -7907,8 +7890,17 @@ class ServerAPI(object):
7907
7890
  valid.
7908
7891
 
7909
7892
  """
7893
+ if thumbnail_id:
7894
+ warnings.warn(
7895
+ (
7896
+ "Function 'get_workfile_thumbnail' got 'thumbnail_id'"
7897
+ " which is deprecated and will be removed in future"
7898
+ " version."
7899
+ ),
7900
+ DeprecationWarning
7901
+ )
7910
7902
  return self.get_thumbnail(
7911
- project_name, "workfile", workfile_id, thumbnail_id
7903
+ project_name, "workfile", workfile_id
7912
7904
  )
7913
7905
 
7914
7906
  def create_thumbnail(
@@ -8345,22 +8337,8 @@ class ServerAPI(object):
8345
8337
  kwargs = {
8346
8338
  "input": input_id,
8347
8339
  "output": output_id,
8340
+ "linkType": full_link_type_name,
8348
8341
  }
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
8342
  if link_name:
8365
8343
  kwargs["name"] = link_name
8366
8344
 
@@ -8522,23 +8500,6 @@ class ServerAPI(object):
8522
8500
  return output
8523
8501
 
8524
8502
  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
8503
  query = query_func(link_fields)
8543
8504
  for attr, filter_value in filters.items():
8544
8505
  query.set_variable_value(attr, filter_value)
@@ -4,6 +4,7 @@ import datetime
4
4
  import uuid
5
5
  import string
6
6
  import platform
7
+ import traceback
7
8
  import collections
8
9
  from urllib.parse import urlparse, urlencode
9
10
  import typing
@@ -322,18 +323,38 @@ def _try_parse_url(url: str) -> Optional[str]:
322
323
 
323
324
 
324
325
  def _try_connect_to_server(
325
- url: str, timeout: Optional[float] = None
326
- ) -> bool:
326
+ url: str,
327
+ timeout: Optional[float],
328
+ verify: Optional["Union[str, bool]"],
329
+ cert: Optional[str],
330
+ ) -> Optional[str]:
327
331
  if timeout is None:
328
332
  timeout = get_default_timeout()
333
+
334
+ if verify is None:
335
+ verify = os.environ.get("AYON_CA_FILE") or True
336
+
337
+ if cert is None:
338
+ cert = os.environ.get("AYON_CERT_FILE") or None
339
+
329
340
  try:
330
341
  # TODO add validation if the url lead to AYON server
331
342
  # - this won't validate if the url lead to 'google.com'
332
- requests.get(url, timeout=timeout)
343
+ response = requests.get(
344
+ url,
345
+ timeout=timeout,
346
+ verify=verify,
347
+ cert=cert,
348
+ )
349
+ if response.history:
350
+ return response.history[-1].headers["location"].rstrip("/")
351
+ return url
333
352
 
334
- except BaseException:
335
- return False
336
- return True
353
+ except Exception:
354
+ print(f"Failed to connect to '{url}'")
355
+ traceback.print_exc()
356
+
357
+ return None
337
358
 
338
359
 
339
360
  def login_to_server(
@@ -463,7 +484,12 @@ def is_token_valid(
463
484
  return False
464
485
 
465
486
 
466
- def validate_url(url: str, timeout: Optional[int] = None) -> str:
487
+ def validate_url(
488
+ url: str,
489
+ timeout: Optional[int] = None,
490
+ verify: Optional["Union[str, bool]"] = None,
491
+ cert: Optional[str] = None,
492
+ ) -> str:
467
493
  """Validate url if is valid and server is available.
468
494
 
469
495
  Validation checks if can be parsed as url and contains scheme.
@@ -520,12 +546,23 @@ def validate_url(url: str, timeout: Optional[int] = None) -> str:
520
546
  # Try add 'https://' scheme if is missing
521
547
  # - this will trigger UrlError if both will crash
522
548
  if not parsed_url.scheme:
523
- new_url = "https://" + modified_url
524
- if _try_connect_to_server(new_url, timeout=timeout):
549
+ new_url = _try_connect_to_server(
550
+ "http://" + modified_url,
551
+ timeout=timeout,
552
+ verify=verify,
553
+ cert=cert,
554
+ )
555
+ if new_url:
525
556
  return new_url
526
557
 
527
- if _try_connect_to_server(modified_url, timeout=timeout):
528
- return modified_url
558
+ new_url = _try_connect_to_server(
559
+ modified_url,
560
+ timeout=timeout,
561
+ verify=verify,
562
+ cert=cert,
563
+ )
564
+ if new_url:
565
+ return new_url
529
566
 
530
567
  hints = []
531
568
  if "/" in parsed_url.path or not parsed_url.scheme:
@@ -855,11 +892,13 @@ def _get_media_mime_type_for_content_base(content: bytes) -> Optional[str]:
855
892
  if content[0:4] == b"\211PNG":
856
893
  return "image/png"
857
894
 
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
- ):
895
+ # JPEG
896
+ # - [0:2] is constant b"\xff\xd8"
897
+ # (ref. https://www.file-recovery.com/jpg-signature-format.htm)
898
+ # - [2:4] Marker identifier b"\xff{?}"
899
+ # (ref. https://www.disktuna.com/list-of-jpeg-markers/)
900
+ # NOTE: File ends with b"\xff\xd9"
901
+ if content[0:3] == b"\xff\xd8\xff":
863
902
  return "image/jpeg"
864
903
 
865
904
  # Webp
@@ -1,2 +1,2 @@
1
1
  """Package declaring Python API for AYON server."""
2
- __version__ = "1.0.12"
2
+ __version__ = "1.1.1"
@@ -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.1
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.1"
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.1"
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 = "*"