ayon-python-api 1.0.6__tar.gz → 1.0.8__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 (23) hide show
  1. {ayon-python-api-1.0.6 → ayon-python-api-1.0.8}/PKG-INFO +1 -1
  2. {ayon-python-api-1.0.6 → ayon-python-api-1.0.8}/ayon_api/__init__.py +12 -0
  3. {ayon-python-api-1.0.6 → ayon-python-api-1.0.8}/ayon_api/_api.py +61 -1
  4. {ayon-python-api-1.0.6 → ayon-python-api-1.0.8}/ayon_api/graphql_queries.py +9 -1
  5. {ayon-python-api-1.0.6 → ayon-python-api-1.0.8}/ayon_api/server_api.py +151 -12
  6. {ayon-python-api-1.0.6 → ayon-python-api-1.0.8}/ayon_api/utils.py +146 -7
  7. {ayon-python-api-1.0.6 → ayon-python-api-1.0.8}/ayon_api/version.py +1 -1
  8. {ayon-python-api-1.0.6 → ayon-python-api-1.0.8}/ayon_python_api.egg-info/PKG-INFO +1 -1
  9. {ayon-python-api-1.0.6 → ayon-python-api-1.0.8}/pyproject.toml +2 -2
  10. {ayon-python-api-1.0.6 → ayon-python-api-1.0.8}/LICENSE +0 -0
  11. {ayon-python-api-1.0.6 → ayon-python-api-1.0.8}/README.md +0 -0
  12. {ayon-python-api-1.0.6 → ayon-python-api-1.0.8}/ayon_api/constants.py +0 -0
  13. {ayon-python-api-1.0.6 → ayon-python-api-1.0.8}/ayon_api/entity_hub.py +0 -0
  14. {ayon-python-api-1.0.6 → ayon-python-api-1.0.8}/ayon_api/events.py +0 -0
  15. {ayon-python-api-1.0.6 → ayon-python-api-1.0.8}/ayon_api/exceptions.py +0 -0
  16. {ayon-python-api-1.0.6 → ayon-python-api-1.0.8}/ayon_api/graphql.py +0 -0
  17. {ayon-python-api-1.0.6 → ayon-python-api-1.0.8}/ayon_api/operations.py +0 -0
  18. {ayon-python-api-1.0.6 → ayon-python-api-1.0.8}/ayon_python_api.egg-info/SOURCES.txt +0 -0
  19. {ayon-python-api-1.0.6 → ayon-python-api-1.0.8}/ayon_python_api.egg-info/dependency_links.txt +0 -0
  20. {ayon-python-api-1.0.6 → ayon-python-api-1.0.8}/ayon_python_api.egg-info/requires.txt +0 -0
  21. {ayon-python-api-1.0.6 → ayon-python-api-1.0.8}/ayon_python_api.egg-info/top_level.txt +0 -0
  22. {ayon-python-api-1.0.6 → ayon-python-api-1.0.8}/setup.cfg +0 -0
  23. {ayon-python-api-1.0.6 → ayon-python-api-1.0.8}/setup.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: ayon-python-api
3
- Version: 1.0.6
3
+ Version: 1.0.8
4
4
  Summary: AYON Python API
5
5
  Home-page: https://github.com/ynput/ayon-python-api
6
6
  Author: ynput.io
@@ -3,6 +3,10 @@ from .utils import (
3
3
  TransferProgress,
4
4
  slugify_string,
5
5
  create_dependency_package_basename,
6
+ get_user_by_token,
7
+ is_token_valid,
8
+ validate_url,
9
+ login_to_server,
6
10
  )
7
11
  from .server_api import (
8
12
  RequestTypes,
@@ -46,6 +50,7 @@ from ._api import (
46
50
  get_server_version,
47
51
  get_server_version_tuple,
48
52
  get_users,
53
+ get_user_by_name,
49
54
  get_user,
50
55
  raw_post,
51
56
  raw_put,
@@ -66,6 +71,7 @@ from ._api import (
66
71
  download_file,
67
72
  upload_file_from_stream,
68
73
  upload_file,
74
+ upload_reviewable,
69
75
  trigger_server_restart,
70
76
  query_graphql,
71
77
  get_graphql_schema,
@@ -228,6 +234,10 @@ __all__ = (
228
234
  "TransferProgress",
229
235
  "slugify_string",
230
236
  "create_dependency_package_basename",
237
+ "get_user_by_token",
238
+ "is_token_valid",
239
+ "validate_url",
240
+ "login_to_server",
231
241
 
232
242
  "RequestTypes",
233
243
  "ServerAPI",
@@ -268,6 +278,7 @@ __all__ = (
268
278
  "get_server_version",
269
279
  "get_server_version_tuple",
270
280
  "get_users",
281
+ "get_user_by_name",
271
282
  "get_user",
272
283
  "raw_post",
273
284
  "raw_put",
@@ -288,6 +299,7 @@ __all__ = (
288
299
  "download_file",
289
300
  "upload_file_from_stream",
290
301
  "upload_file",
302
+ "upload_reviewable",
291
303
  "trigger_server_restart",
292
304
  "query_graphql",
293
305
  "get_graphql_schema",
@@ -594,9 +594,13 @@ def get_server_version_tuple():
594
594
  def get_users(*args, **kwargs):
595
595
  """Get Users.
596
596
 
597
+ Only administrators and managers can fetch all users. For other users
598
+ it is required to pass in 'project_name' filter.
599
+
597
600
  Args:
601
+ project_name (Optional[str]): Project name.
598
602
  usernames (Optional[Iterable[str]]): Filter by usernames.
599
- fields (Optional[Iterable[str]]): fields to be queried
603
+ fields (Optional[Iterable[str]]): Fields to be queried
600
604
  for users.
601
605
 
602
606
  Returns:
@@ -607,7 +611,38 @@ def get_users(*args, **kwargs):
607
611
  return con.get_users(*args, **kwargs)
608
612
 
609
613
 
614
+ def get_user_by_name(*args, **kwargs):
615
+ """Get user by name using GraphQl.
616
+
617
+ Only administrators and managers can fetch all users. For other users
618
+ it is required to pass in 'project_name' filter.
619
+
620
+ Args:
621
+ username (str): Username.
622
+ project_name (Optional[str]): Define scope of project.
623
+ fields (Optional[Iterable[str]]): Fields to be queried
624
+ for users.
625
+
626
+ Returns:
627
+ Union[dict[str, Any], None]: User info or None if user is not
628
+ found.
629
+
630
+ """
631
+ con = get_server_api_connection()
632
+ return con.get_user_by_name(*args, **kwargs)
633
+
634
+
610
635
  def get_user(*args, **kwargs):
636
+ """Get user info using REST endpoit.
637
+
638
+ Args:
639
+ username (Optional[str]): Username.
640
+
641
+ Returns:
642
+ Union[dict[str, Any], None]: User info or None if user is not
643
+ found.
644
+
645
+ """
611
646
  con = get_server_api_connection()
612
647
  return con.get_user(*args, **kwargs)
613
648
 
@@ -903,6 +938,29 @@ def upload_file(*args, **kwargs):
903
938
  return con.upload_file(*args, **kwargs)
904
939
 
905
940
 
941
+ def upload_reviewable(*args, **kwargs):
942
+ """Upload reviewable file to server.
943
+
944
+ Args:
945
+ project_name (str): Project name.
946
+ version_id (str): Version id.
947
+ filepath (str): Reviewable file path to upload.
948
+ label (Optional[str]): Reviewable label. Filled automatically
949
+ server side with filename.
950
+ content_type (Optional[str]): MIME type of the file.
951
+ filename (Optional[str]): User as original filename. Filename from
952
+ 'filepath' is used when not filled.
953
+ progress (Optional[TransferProgress]): Progress.
954
+ headers (Optional[Dict[str, Any]]): Headers.
955
+
956
+ Returns:
957
+ RestApiResponse: Server response.
958
+
959
+ """
960
+ con = get_server_api_connection()
961
+ return con.upload_reviewable(*args, **kwargs)
962
+
963
+
906
964
  def trigger_server_restart():
907
965
  """Trigger server restart.
908
966
 
@@ -3310,6 +3368,7 @@ def get_representations_hierarchy(*args, **kwargs):
3310
3368
  representation_ids (Iterable[str]): Representation ids.
3311
3369
  project_fields (Optional[Iterable[str]]): Project fields.
3312
3370
  folder_fields (Optional[Iterable[str]]): Folder fields.
3371
+ task_fields (Optional[Iterable[str]]): Task fields.
3313
3372
  product_fields (Optional[Iterable[str]]): Product fields.
3314
3373
  version_fields (Optional[Iterable[str]]): Version fields.
3315
3374
  representation_fields (Optional[Iterable[str]]): Representation
@@ -3334,6 +3393,7 @@ def get_representation_hierarchy(*args, **kwargs):
3334
3393
  representation_id (str): Representation id.
3335
3394
  project_fields (Optional[Iterable[str]]): Project fields.
3336
3395
  folder_fields (Optional[Iterable[str]]): Folder fields.
3396
+ task_fields (Optional[Iterable[str]]): Task fields.
3337
3397
  product_fields (Optional[Iterable[str]]): Product fields.
3338
3398
  version_fields (Optional[Iterable[str]]): Version fields.
3339
3399
  representation_fields (Optional[Iterable[str]]): Representation
@@ -466,6 +466,7 @@ def representations_parents_qraphql_query(
466
466
 
467
467
  def representations_hierarchy_qraphql_query(
468
468
  folder_fields,
469
+ task_fields,
469
470
  product_fields,
470
471
  version_fields,
471
472
  representation_fields,
@@ -486,12 +487,17 @@ def representations_hierarchy_qraphql_query(
486
487
 
487
488
  repres_field.set_filter("ids", repre_ids_var)
488
489
  version_field = None
489
- if folder_fields or product_fields or version_fields:
490
+ if folder_fields or task_fields or product_fields or version_fields:
490
491
  version_field = repres_field.add_field("version")
491
492
  if version_fields:
492
493
  for key, value in fields_to_dict(version_fields).items():
493
494
  fields_queue.append((key, value, version_field))
494
495
 
496
+ if task_fields:
497
+ task_field = version_field.add_field("task")
498
+ for key, value in fields_to_dict(task_fields).items():
499
+ fields_queue.append((key, value, task_field))
500
+
495
501
  product_field = None
496
502
  if folder_fields or product_fields:
497
503
  product_field = version_field.add_field("product")
@@ -600,9 +606,11 @@ def events_graphql_query(fields):
600
606
  def users_graphql_query(fields):
601
607
  query = GraphQlQuery("Users")
602
608
  names_var = query.add_variable("userNames", "[String!]")
609
+ project_name_var = query.add_variable("projectName", "String!")
603
610
 
604
611
  users_field = query.add_field_with_edges("users")
605
612
  users_field.set_filter("names", names_var)
613
+ users_field.set_filter("projectName", project_name_var)
606
614
 
607
615
  nested_fields = fields_to_dict(set(fields))
608
616
 
@@ -90,6 +90,7 @@ from .utils import (
90
90
  get_default_settings_variant,
91
91
  get_default_site_id,
92
92
  NOT_SET,
93
+ get_media_mime_type,
93
94
  )
94
95
 
95
96
  PatternType = type(re.compile(""))
@@ -1028,12 +1029,16 @@ class ServerAPI(object):
1028
1029
  self._access_token_is_service = None
1029
1030
  return None
1030
1031
 
1031
- def get_users(self, usernames=None, fields=None):
1032
+ def get_users(self, project_name=None, usernames=None, fields=None):
1032
1033
  """Get Users.
1033
1034
 
1035
+ Only administrators and managers can fetch all users. For other users
1036
+ it is required to pass in 'project_name' filter.
1037
+
1034
1038
  Args:
1039
+ project_name (Optional[str]): Project name.
1035
1040
  usernames (Optional[Iterable[str]]): Filter by usernames.
1036
- fields (Optional[Iterable[str]]): fields to be queried
1041
+ fields (Optional[Iterable[str]]): Fields to be queried
1037
1042
  for users.
1038
1043
 
1039
1044
  Returns:
@@ -1047,6 +1052,9 @@ class ServerAPI(object):
1047
1052
  return
1048
1053
  filters["userNames"] = list(usernames)
1049
1054
 
1055
+ if project_name is not None:
1056
+ filters["projectName"] = project_name
1057
+
1050
1058
  if not fields:
1051
1059
  fields = self.get_default_fields_for_type("user")
1052
1060
 
@@ -1060,7 +1068,45 @@ class ServerAPI(object):
1060
1068
  user["accessGroups"])
1061
1069
  yield user
1062
1070
 
1071
+ def get_user_by_name(self, username, project_name=None, fields=None):
1072
+ """Get user by name using GraphQl.
1073
+
1074
+ Only administrators and managers can fetch all users. For other users
1075
+ it is required to pass in 'project_name' filter.
1076
+
1077
+ Args:
1078
+ username (str): Username.
1079
+ project_name (Optional[str]): Define scope of project.
1080
+ fields (Optional[Iterable[str]]): Fields to be queried
1081
+ for users.
1082
+
1083
+ Returns:
1084
+ Union[dict[str, Any], None]: User info or None if user is not
1085
+ found.
1086
+
1087
+ """
1088
+ if not username:
1089
+ return None
1090
+
1091
+ for user in self.get_users(
1092
+ project_name=project_name,
1093
+ usernames={username},
1094
+ fields=fields,
1095
+ ):
1096
+ return user
1097
+ return None
1098
+
1063
1099
  def get_user(self, username=None):
1100
+ """Get user info using REST endpoit.
1101
+
1102
+ Args:
1103
+ username (Optional[str]): Username.
1104
+
1105
+ Returns:
1106
+ Union[dict[str, Any], None]: User info or None if user is not
1107
+ found.
1108
+
1109
+ """
1064
1110
  if username is None:
1065
1111
  output = self._get_user_info()
1066
1112
  if output is None:
@@ -1607,7 +1653,13 @@ class ServerAPI(object):
1607
1653
  response = self.post("enroll", **kwargs)
1608
1654
  if response.status_code == 204:
1609
1655
  return None
1610
- elif response.status_code >= 400:
1656
+
1657
+ if response.status_code == 503:
1658
+ # Server is busy
1659
+ self.log.info("Server is busy. Can't enroll event now.")
1660
+ return None
1661
+
1662
+ if response.status_code >= 400:
1611
1663
  self.log.error(response.text)
1612
1664
  return None
1613
1665
 
@@ -1886,6 +1938,77 @@ class ServerAPI(object):
1886
1938
  endpoint, stream, progress, request_type, **kwargs
1887
1939
  )
1888
1940
 
1941
+ def upload_reviewable(
1942
+ self,
1943
+ project_name,
1944
+ version_id,
1945
+ filepath,
1946
+ label=None,
1947
+ content_type=None,
1948
+ filename=None,
1949
+ progress=None,
1950
+ headers=None,
1951
+ **kwargs
1952
+ ):
1953
+ """Upload reviewable file to server.
1954
+
1955
+ Args:
1956
+ project_name (str): Project name.
1957
+ version_id (str): Version id.
1958
+ filepath (str): Reviewable file path to upload.
1959
+ label (Optional[str]): Reviewable label. Filled automatically
1960
+ server side with filename.
1961
+ content_type (Optional[str]): MIME type of the file.
1962
+ filename (Optional[str]): User as original filename. Filename from
1963
+ 'filepath' is used when not filled.
1964
+ progress (Optional[TransferProgress]): Progress.
1965
+ headers (Optional[Dict[str, Any]]): Headers.
1966
+
1967
+ Returns:
1968
+ RestApiResponse: Server response.
1969
+
1970
+ """
1971
+ if not content_type:
1972
+ content_type = get_media_mime_type(filepath)
1973
+
1974
+ if not content_type:
1975
+ raise ValueError(
1976
+ f"Could not determine MIME type of file '{filepath}'"
1977
+ )
1978
+
1979
+ if headers is None:
1980
+ headers = self.get_headers(content_type)
1981
+ else:
1982
+ # Make sure content-type is filled with file content type
1983
+ content_type_key = next(
1984
+ (
1985
+ key
1986
+ for key in headers
1987
+ if key.lower() == "content-type"
1988
+ ),
1989
+ "Content-Type"
1990
+ )
1991
+ headers[content_type_key] = content_type
1992
+
1993
+ # Fill original filename if not explicitly defined
1994
+ if not filename:
1995
+ filename = os.path.basename(filepath)
1996
+ headers["x-file-name"] = filename
1997
+
1998
+ query = f"?label={label}" if label else ""
1999
+ endpoint = (
2000
+ f"/projects/{project_name}"
2001
+ f"/versions/{version_id}/reviewables{query}"
2002
+ )
2003
+ return self.upload_file(
2004
+ endpoint,
2005
+ filepath,
2006
+ progress=progress,
2007
+ headers=headers,
2008
+ request_type=RequestTypes.post,
2009
+ **kwargs
2010
+ )
2011
+
1889
2012
  def trigger_server_restart(self):
1890
2013
  """Trigger server restart.
1891
2014
 
@@ -6336,6 +6459,7 @@ class ServerAPI(object):
6336
6459
  representation_ids,
6337
6460
  project_fields=None,
6338
6461
  folder_fields=None,
6462
+ task_fields=None,
6339
6463
  product_fields=None,
6340
6464
  version_fields=None,
6341
6465
  representation_fields=None,
@@ -6353,6 +6477,7 @@ class ServerAPI(object):
6353
6477
  representation_ids (Iterable[str]): Representation ids.
6354
6478
  project_fields (Optional[Iterable[str]]): Project fields.
6355
6479
  folder_fields (Optional[Iterable[str]]): Folder fields.
6480
+ task_fields (Optional[Iterable[str]]): Task fields.
6356
6481
  product_fields (Optional[Iterable[str]]): Product fields.
6357
6482
  version_fields (Optional[Iterable[str]]): Version fields.
6358
6483
  representation_fields (Optional[Iterable[str]]): Representation
@@ -6383,7 +6508,7 @@ class ServerAPI(object):
6383
6508
  repre_ids = set(representation_ids)
6384
6509
  output = {
6385
6510
  repre_id: RepresentationHierarchy(
6386
- project, None, None, None, None
6511
+ project, None, None, None, None, None
6387
6512
  )
6388
6513
  for repre_id in representation_ids
6389
6514
  }
@@ -6393,6 +6518,11 @@ class ServerAPI(object):
6393
6518
  else:
6394
6519
  folder_fields = set(folder_fields)
6395
6520
 
6521
+ if task_fields is None:
6522
+ task_fields = self.get_default_fields_for_type("task")
6523
+ else:
6524
+ task_fields = set(task_fields)
6525
+
6396
6526
  if product_fields is None:
6397
6527
  product_fields = self.get_default_fields_for_type("product")
6398
6528
  else:
@@ -6414,6 +6544,7 @@ class ServerAPI(object):
6414
6544
 
6415
6545
  query = representations_hierarchy_qraphql_query(
6416
6546
  folder_fields,
6547
+ task_fields,
6417
6548
  product_fields,
6418
6549
  version_fields,
6419
6550
  representation_fields,
@@ -6426,12 +6557,16 @@ class ServerAPI(object):
6426
6557
  repre_id = repre["id"]
6427
6558
  version = repre.pop("version", {})
6428
6559
  product = version.pop("product", {})
6560
+ task = version.pop("task", None)
6429
6561
  folder = product.pop("folder", {})
6430
6562
  self._convert_entity_data(version)
6431
6563
  self._convert_entity_data(product)
6432
6564
  self._convert_entity_data(folder)
6565
+ if task:
6566
+ self._convert_entity_data(task)
6567
+
6433
6568
  output[repre_id] = RepresentationHierarchy(
6434
- project, folder, product, version, repre
6569
+ project, folder, task, product, version, repre
6435
6570
  )
6436
6571
 
6437
6572
  return output
@@ -6442,6 +6577,7 @@ class ServerAPI(object):
6442
6577
  representation_id,
6443
6578
  project_fields=None,
6444
6579
  folder_fields=None,
6580
+ task_fields=None,
6445
6581
  product_fields=None,
6446
6582
  version_fields=None,
6447
6583
  representation_fields=None,
@@ -6455,6 +6591,7 @@ class ServerAPI(object):
6455
6591
  representation_id (str): Representation id.
6456
6592
  project_fields (Optional[Iterable[str]]): Project fields.
6457
6593
  folder_fields (Optional[Iterable[str]]): Folder fields.
6594
+ task_fields (Optional[Iterable[str]]): Task fields.
6458
6595
  product_fields (Optional[Iterable[str]]): Product fields.
6459
6596
  version_fields (Optional[Iterable[str]]): Version fields.
6460
6597
  representation_fields (Optional[Iterable[str]]): Representation
@@ -6472,6 +6609,7 @@ class ServerAPI(object):
6472
6609
  [representation_id],
6473
6610
  project_fields=project_fields,
6474
6611
  folder_fields=folder_fields,
6612
+ task_fields=task_fields,
6475
6613
  product_fields=product_fields,
6476
6614
  version_fields=version_fields,
6477
6615
  representation_fields=representation_fields,
@@ -6509,6 +6647,7 @@ class ServerAPI(object):
6509
6647
  representation_ids,
6510
6648
  project_fields=project_fields,
6511
6649
  folder_fields=folder_fields,
6650
+ task_fields=set(),
6512
6651
  product_fields=product_fields,
6513
6652
  version_fields=version_fields,
6514
6653
  representation_fields={"id"},
@@ -8073,11 +8212,11 @@ class ServerAPI(object):
8073
8212
  return op_results
8074
8213
 
8075
8214
  def _convert_entity_data(self, entity):
8076
- if not entity:
8215
+ if not entity or "data" not in entity:
8077
8216
  return
8078
- entity_data = entity.get("data")
8079
- if (
8080
- entity_data is not None
8081
- and isinstance(entity_data, str)
8082
- ):
8083
- entity["data"] = json.loads(entity_data)
8217
+
8218
+ entity_data = entity["data"] or {}
8219
+ if isinstance(entity_data, str):
8220
+ entity_data = json.loads(entity_data)
8221
+
8222
+ entity["data"] = entity_data
@@ -6,6 +6,7 @@ import string
6
6
  import platform
7
7
  import collections
8
8
  from urllib.parse import urlparse, urlencode
9
+ from typing import Optional
9
10
 
10
11
  import requests
11
12
  import unidecode
@@ -29,7 +30,14 @@ RepresentationParents = collections.namedtuple(
29
30
 
30
31
  RepresentationHierarchy = collections.namedtuple(
31
32
  "RepresentationHierarchy",
32
- ("project", "folder", "product", "version", "representation")
33
+ (
34
+ "project",
35
+ "folder",
36
+ "task",
37
+ "product",
38
+ "version",
39
+ "representation",
40
+ )
33
41
  )
34
42
 
35
43
 
@@ -341,10 +349,8 @@ def logout_from_server(url, token, timeout=None):
341
349
  )
342
350
 
343
351
 
344
- def is_token_valid(url, token, timeout=None):
345
- """Check if token is valid.
346
-
347
- Token can be a user token or service api key.
352
+ def get_user_by_token(url, token, timeout=None):
353
+ """Get user information by url and token.
348
354
 
349
355
  Args:
350
356
  url (str): Server url.
@@ -353,7 +359,7 @@ def is_token_valid(url, token, timeout=None):
353
359
  'get_default_timeout' is used if not specified.
354
360
 
355
361
  Returns:
356
- bool: True if token is valid.
362
+ Optional[Dict[str, Any]]: User information if url and token are valid.
357
363
 
358
364
  """
359
365
  if timeout is None:
@@ -374,7 +380,27 @@ def is_token_valid(url, token, timeout=None):
374
380
  timeout=timeout,
375
381
  )
376
382
  if response.status_code == 200:
377
- return True
383
+ return response.json()
384
+ return None
385
+
386
+
387
+ def is_token_valid(url, token, timeout=None):
388
+ """Check if token is valid.
389
+
390
+ Token can be a user token or service api key.
391
+
392
+ Args:
393
+ url (str): Server url.
394
+ token (str): User's token.
395
+ timeout (Optional[float]): Timeout for request. Value from
396
+ 'get_default_timeout' is used if not specified.
397
+
398
+ Returns:
399
+ bool: True if token is valid.
400
+
401
+ """
402
+ if get_user_by_token(url, token, timeout=timeout):
403
+ return True
378
404
  return False
379
405
 
380
406
 
@@ -697,3 +723,116 @@ def create_dependency_package_basename(platform_name=None):
697
723
  now_date = datetime.datetime.now()
698
724
  time_stamp = now_date.strftime("%y%m%d%H%M")
699
725
  return "ayon_{}_{}".format(time_stamp, platform_name)
726
+
727
+
728
+
729
+ def _get_media_mime_type_from_ftyp(content):
730
+ if content[8:10] == b"qt" or content[8:12] == b"MSNV":
731
+ return "video/quicktime"
732
+
733
+ if content[8:12] in (b"3g2a", b"3g2b", b"3g2c", b"KDDI"):
734
+ return "video/3gpp2"
735
+
736
+ if content[8:12] in (
737
+ b"isom", b"iso2", b"avc1", b"F4V", b"F4P", b"F4A", b"F4B", b"mmp4",
738
+ # These might be "video/mp4v"
739
+ b"mp41", b"mp42",
740
+ # Nero
741
+ b"NDSC", b"NDSH", b"NDSM", b"NDSP", b"NDSS", b"NDXC", b"NDXH",
742
+ b"NDXM", b"NDXP", b"NDXS",
743
+ ):
744
+ return "video/mp4"
745
+
746
+ if content[8:12] in (
747
+ b"3ge6", b"3ge7", b"3gg6",
748
+ b"3gp1", b"3gp2", b"3gp3", b"3gp4", b"3gp5", b"3gp6", b"3gs7",
749
+ ):
750
+ return "video/3gpp"
751
+
752
+ if content[8:11] == b"JP2":
753
+ return "image/jp2"
754
+
755
+ if content[8:11] == b"jpm":
756
+ return "image/jpm"
757
+
758
+ if content[8:11] == b"jpx":
759
+ return "image/jpx"
760
+
761
+ if content[8:12] in (b"M4V\x20", b"M4VH", b"M4VP"):
762
+ return "video/x-m4v"
763
+
764
+ if content[8:12] in (b"mj2s", b"mjp2"):
765
+ return "video/mj2"
766
+ return None
767
+
768
+
769
+ def get_media_mime_type_for_content(content: bytes) -> Optional[str]:
770
+ content_len = len(content)
771
+ # Pre-validation (largest definition check)
772
+ # - hopefully there cannot be media defined in less than 12 bytes
773
+ if content_len < 12:
774
+ return None
775
+
776
+ # FTYP
777
+ if content[4:8] == b"ftyp":
778
+ return _get_media_mime_type_from_ftyp(content)
779
+
780
+ # BMP
781
+ if content[0:2] == b"BM":
782
+ return "image/bmp"
783
+
784
+ # Tiff
785
+ if content[0:2] in (b"MM", b"II"):
786
+ return "tiff"
787
+
788
+ # PNG
789
+ if content[0:4] == b"\211PNG":
790
+ return "image/png"
791
+
792
+ # SVG
793
+ if b'xmlns="http://www.w3.org/2000/svg"' in content:
794
+ return "image/svg+xml"
795
+
796
+ # JPEG, JFIF or Exif
797
+ if (
798
+ content[0:4] == b"\xff\xd8\xff\xdb"
799
+ or content[6:10] in (b"JFIF", b"Exif")
800
+ ):
801
+ return "image/jpeg"
802
+
803
+ # Webp
804
+ if content[0:4] == b"RIFF" and content[8:12] == b"WEBP":
805
+ return "image/webp"
806
+
807
+ # Gif
808
+ if content[0:6] in (b"GIF87a", b"GIF89a"):
809
+ return "gif"
810
+
811
+ # Adobe PhotoShop file (8B > Adobe, PS > PhotoShop)
812
+ if content[0:4] == b"8BPS":
813
+ return "image/vnd.adobe.photoshop"
814
+
815
+ # Windows ICO > this might be wild guess as multiple files can start
816
+ # with this header
817
+ if content[0:4] == b"\x00\x00\x01\x00":
818
+ return "image/x-icon"
819
+ return None
820
+
821
+
822
+ def get_media_mime_type(filepath: str) -> Optional[str]:
823
+ """Determine Mime-Type of a file.
824
+
825
+ Args:
826
+ filepath (str): Path to file.
827
+
828
+ Returns:
829
+ Optional[str]: Mime type or None if is unknown mime type.
830
+
831
+ """
832
+ if not filepath or not os.path.exists(filepath):
833
+ return None
834
+
835
+ with open(filepath, "rb") as stream:
836
+ content = stream.read()
837
+
838
+ return get_media_mime_type_for_content(content)
@@ -1,2 +1,2 @@
1
1
  """Package declaring Python API for AYON server."""
2
- __version__ = "1.0.6"
2
+ __version__ = "1.0.8"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: ayon-python-api
3
- Version: 1.0.6
3
+ Version: 1.0.8
4
4
  Summary: AYON Python API
5
5
  Home-page: https://github.com/ynput/ayon-python-api
6
6
  Author: ynput.io
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "ayon-python-api"
3
- version = "1.0.6"
3
+ version = "1.0.8"
4
4
  description = "AYON Python API"
5
5
  license = {file = "LICENSE"}
6
6
  readme = {file = "README.md", content-type = "text/markdown"}
@@ -29,7 +29,7 @@ build-backend = "poetry.core.masonry.api"
29
29
 
30
30
  [tool.poetry]
31
31
  name = "ayon-python-api"
32
- version = "1.0.6"
32
+ version = "1.0.8"
33
33
  description = "AYON Python API"
34
34
  authors = [
35
35
  "ynput.io <info@ynput.io>"
File without changes