ayon-python-api 1.2.11__tar.gz → 1.2.13__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 (49) hide show
  1. {ayon_python_api-1.2.11 → ayon_python_api-1.2.13}/PKG-INFO +1 -1
  2. {ayon_python_api-1.2.11 → ayon_python_api-1.2.13}/ayon_api/__init__.py +8 -0
  3. {ayon_python_api-1.2.11 → ayon_python_api-1.2.13}/ayon_api/_api.py +145 -1
  4. {ayon_python_api-1.2.11 → ayon_python_api-1.2.13}/ayon_api/_api_helpers/base.py +6 -0
  5. {ayon_python_api-1.2.11 → ayon_python_api-1.2.13}/ayon_api/_api_helpers/bundles_addons.py +15 -0
  6. {ayon_python_api-1.2.11 → ayon_python_api-1.2.13}/ayon_api/_api_helpers/folders.py +2 -0
  7. {ayon_python_api-1.2.11 → ayon_python_api-1.2.13}/ayon_api/_api_helpers/links.py +1 -0
  8. {ayon_python_api-1.2.11 → ayon_python_api-1.2.13}/ayon_api/_api_helpers/products.py +2 -0
  9. {ayon_python_api-1.2.11 → ayon_python_api-1.2.13}/ayon_api/_api_helpers/representations.py +2 -0
  10. {ayon_python_api-1.2.11 → ayon_python_api-1.2.13}/ayon_api/_api_helpers/tasks.py +4 -0
  11. {ayon_python_api-1.2.11 → ayon_python_api-1.2.13}/ayon_api/_api_helpers/versions.py +2 -1
  12. {ayon_python_api-1.2.11 → ayon_python_api-1.2.13}/ayon_api/_api_helpers/workfiles.py +2 -0
  13. {ayon_python_api-1.2.11 → ayon_python_api-1.2.13}/ayon_api/graphql_queries.py +25 -10
  14. {ayon_python_api-1.2.11 → ayon_python_api-1.2.13}/ayon_api/operations.py +2 -2
  15. {ayon_python_api-1.2.11 → ayon_python_api-1.2.13}/ayon_api/server_api.py +176 -19
  16. {ayon_python_api-1.2.11 → ayon_python_api-1.2.13}/ayon_api/utils.py +28 -11
  17. {ayon_python_api-1.2.11 → ayon_python_api-1.2.13}/ayon_api/version.py +1 -1
  18. {ayon_python_api-1.2.11 → ayon_python_api-1.2.13}/ayon_python_api.egg-info/PKG-INFO +1 -1
  19. {ayon_python_api-1.2.11 → ayon_python_api-1.2.13}/pyproject.toml +3 -3
  20. {ayon_python_api-1.2.11 → ayon_python_api-1.2.13}/LICENSE +0 -0
  21. {ayon_python_api-1.2.11 → ayon_python_api-1.2.13}/README.md +0 -0
  22. {ayon_python_api-1.2.11 → ayon_python_api-1.2.13}/ayon_api/_api_helpers/__init__.py +0 -0
  23. {ayon_python_api-1.2.11 → ayon_python_api-1.2.13}/ayon_api/_api_helpers/actions.py +0 -0
  24. {ayon_python_api-1.2.11 → ayon_python_api-1.2.13}/ayon_api/_api_helpers/activities.py +0 -0
  25. {ayon_python_api-1.2.11 → ayon_python_api-1.2.13}/ayon_api/_api_helpers/attributes.py +0 -0
  26. {ayon_python_api-1.2.11 → ayon_python_api-1.2.13}/ayon_api/_api_helpers/dependency_packages.py +0 -0
  27. {ayon_python_api-1.2.11 → ayon_python_api-1.2.13}/ayon_api/_api_helpers/events.py +0 -0
  28. {ayon_python_api-1.2.11 → ayon_python_api-1.2.13}/ayon_api/_api_helpers/installers.py +0 -0
  29. {ayon_python_api-1.2.11 → ayon_python_api-1.2.13}/ayon_api/_api_helpers/lists.py +0 -0
  30. {ayon_python_api-1.2.11 → ayon_python_api-1.2.13}/ayon_api/_api_helpers/projects.py +0 -0
  31. {ayon_python_api-1.2.11 → ayon_python_api-1.2.13}/ayon_api/_api_helpers/secrets.py +0 -0
  32. {ayon_python_api-1.2.11 → ayon_python_api-1.2.13}/ayon_api/_api_helpers/thumbnails.py +0 -0
  33. {ayon_python_api-1.2.11 → ayon_python_api-1.2.13}/ayon_api/constants.py +0 -0
  34. {ayon_python_api-1.2.11 → ayon_python_api-1.2.13}/ayon_api/entity_hub.py +0 -0
  35. {ayon_python_api-1.2.11 → ayon_python_api-1.2.13}/ayon_api/events.py +0 -0
  36. {ayon_python_api-1.2.11 → ayon_python_api-1.2.13}/ayon_api/exceptions.py +0 -0
  37. {ayon_python_api-1.2.11 → ayon_python_api-1.2.13}/ayon_api/graphql.py +0 -0
  38. {ayon_python_api-1.2.11 → ayon_python_api-1.2.13}/ayon_api/typing.py +0 -0
  39. {ayon_python_api-1.2.11 → ayon_python_api-1.2.13}/ayon_python_api.egg-info/SOURCES.txt +0 -0
  40. {ayon_python_api-1.2.11 → ayon_python_api-1.2.13}/ayon_python_api.egg-info/dependency_links.txt +0 -0
  41. {ayon_python_api-1.2.11 → ayon_python_api-1.2.13}/ayon_python_api.egg-info/requires.txt +0 -0
  42. {ayon_python_api-1.2.11 → ayon_python_api-1.2.13}/ayon_python_api.egg-info/top_level.txt +0 -0
  43. {ayon_python_api-1.2.11 → ayon_python_api-1.2.13}/setup.cfg +0 -0
  44. {ayon_python_api-1.2.11 → ayon_python_api-1.2.13}/setup.py +0 -0
  45. {ayon_python_api-1.2.11 → ayon_python_api-1.2.13}/tests/test_entity_hub.py +0 -0
  46. {ayon_python_api-1.2.11 → ayon_python_api-1.2.13}/tests/test_folder_hierarchy.py +0 -0
  47. {ayon_python_api-1.2.11 → ayon_python_api-1.2.13}/tests/test_get_events.py +0 -0
  48. {ayon_python_api-1.2.11 → ayon_python_api-1.2.13}/tests/test_graphql_queries.py +0 -0
  49. {ayon_python_api-1.2.11 → ayon_python_api-1.2.13}/tests/test_server.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ayon_python_api
3
- Version: 1.2.11
3
+ Version: 1.2.13
4
4
  Summary: AYON Python API
5
5
  Home-page: https://github.com/ynput/ayon-python-api
6
6
  Author: ynput.io
@@ -55,6 +55,7 @@ from ._api import (
55
55
  get_server_version,
56
56
  get_server_version_tuple,
57
57
  is_product_base_type_supported,
58
+ links_graphql_support_data,
58
59
  get_users,
59
60
  get_user_by_name,
60
61
  get_user,
@@ -70,8 +71,11 @@ from ._api import (
70
71
  delete,
71
72
  download_file_to_stream,
72
73
  download_file,
74
+ upload_project_file,
75
+ upload_project_file_from_stream,
73
76
  download_project_file,
74
77
  download_project_file_to_stream,
78
+ delete_project_file,
75
79
  upload_file_from_stream,
76
80
  upload_file,
77
81
  upload_reviewable,
@@ -336,6 +340,7 @@ __all__ = (
336
340
  "get_server_version",
337
341
  "get_server_version_tuple",
338
342
  "is_product_base_type_supported",
343
+ "links_graphql_support_data",
339
344
  "get_users",
340
345
  "get_user_by_name",
341
346
  "get_user",
@@ -351,8 +356,11 @@ __all__ = (
351
356
  "delete",
352
357
  "download_file_to_stream",
353
358
  "download_file",
359
+ "upload_project_file",
360
+ "upload_project_file_from_stream",
354
361
  "download_project_file",
355
362
  "download_project_file_to_stream",
363
+ "delete_project_file",
356
364
  "upload_file_from_stream",
357
365
  "upload_file",
358
366
  "upload_reviewable",
@@ -729,6 +729,13 @@ def is_product_base_type_supported() -> bool:
729
729
  return con.is_product_base_type_supported()
730
730
 
731
731
 
732
+ def links_graphql_support_data() -> bool:
733
+ """Links data can be received by GraphQl.
734
+ """
735
+ con = get_server_api_connection()
736
+ return con.links_graphql_support_data()
737
+
738
+
732
739
  def get_users(
733
740
  project_name: Optional[str] = None,
734
741
  usernames: Optional[Iterable[str]] = None,
@@ -993,6 +1000,99 @@ def download_file(
993
1000
  )
994
1001
 
995
1002
 
1003
+ def upload_project_file(
1004
+ project_name: str,
1005
+ filepath: str,
1006
+ *,
1007
+ content_type: Optional[str] = None,
1008
+ filename: Optional[str] = None,
1009
+ file_id: Optional[str] = None,
1010
+ activity_id: Optional[str] = None,
1011
+ chunk_size: Optional[int] = None,
1012
+ progress: Optional[TransferProgress] = None,
1013
+ ) -> requests.Response:
1014
+ """Upload project file from a filepath.
1015
+
1016
+ Project files are usually binary files, such as images, videos,
1017
+ or other media files that can be accessed via api endpoint
1018
+ '{server url}/api/projects/{project_name}/files/{file_id}'.
1019
+
1020
+ Args:
1021
+ project_name (str): Project name.
1022
+ filepath (str): Path where file will be downloaded.
1023
+ content_type (Optional[str]): MIME type of file.
1024
+ filename (Optional[str]): Server filename, filename from filepath
1025
+ is used if not passed.
1026
+ file_id (Optional[str]): File id.
1027
+ activity_id (Optional[str]): To which activity is file related.
1028
+ chunk_size (Optional[int]): Size of chunks that are received
1029
+ in single loop.
1030
+ progress (Optional[TransferProgress]): Object that gives ability
1031
+ to track download progress.
1032
+
1033
+ Returns:
1034
+ requests.Response: Requests response.
1035
+
1036
+ """
1037
+ con = get_server_api_connection()
1038
+ return con.upload_project_file(
1039
+ project_name=project_name,
1040
+ filepath=filepath,
1041
+ content_type=content_type,
1042
+ filename=filename,
1043
+ file_id=file_id,
1044
+ activity_id=activity_id,
1045
+ chunk_size=chunk_size,
1046
+ progress=progress,
1047
+ )
1048
+
1049
+
1050
+ def upload_project_file_from_stream(
1051
+ project_name: str,
1052
+ stream: StreamType,
1053
+ filename: str,
1054
+ *,
1055
+ content_type: Optional[str] = None,
1056
+ file_id: Optional[str] = None,
1057
+ activity_id: Optional[str] = None,
1058
+ chunk_size: Optional[int] = None,
1059
+ progress: Optional[TransferProgress] = None,
1060
+ ) -> requests.Response:
1061
+ """Upload project file from a filepath.
1062
+
1063
+ Project files are usually binary files, such as images, videos,
1064
+ or other media files that can be accessed via api endpoint
1065
+ '{server url}/api/projects/{project_name}/files/{file_id}'.
1066
+
1067
+ Args:
1068
+ project_name (str): Project name.
1069
+ stream (StreamType): Stream used as source for upload.
1070
+ filename (str): Name of file on server.
1071
+ content_type (Optional[str]): MIME type of file.
1072
+ file_id (Optional[str]): File id.
1073
+ activity_id (Optional[str]): To which activity is file related.
1074
+ chunk_size (Optional[int]): Size of chunks that are received
1075
+ in single loop.
1076
+ progress (Optional[TransferProgress]): Object that gives ability
1077
+ to track download progress.
1078
+
1079
+ Returns:
1080
+ requests.Response: Requests response.
1081
+
1082
+ """
1083
+ con = get_server_api_connection()
1084
+ return con.upload_project_file_from_stream(
1085
+ project_name=project_name,
1086
+ stream=stream,
1087
+ filename=filename,
1088
+ content_type=content_type,
1089
+ file_id=file_id,
1090
+ activity_id=activity_id,
1091
+ chunk_size=chunk_size,
1092
+ progress=progress,
1093
+ )
1094
+
1095
+
996
1096
  def download_project_file(
997
1097
  project_name: str,
998
1098
  file_id: str,
@@ -1067,11 +1167,27 @@ def download_project_file_to_stream(
1067
1167
  )
1068
1168
 
1069
1169
 
1170
+ def delete_project_file(
1171
+ project_name: str,
1172
+ file_id: str,
1173
+ ) -> None:
1174
+ """Delete project file.
1175
+ """
1176
+ con = get_server_api_connection()
1177
+ return con.delete_project_file(
1178
+ project_name=project_name,
1179
+ file_id=file_id,
1180
+ )
1181
+
1182
+
1070
1183
  def upload_file_from_stream(
1071
1184
  endpoint: str,
1072
1185
  stream: StreamType,
1073
1186
  progress: Optional[TransferProgress] = None,
1074
1187
  request_type: Optional[RequestType] = None,
1188
+ *,
1189
+ content_type: Optional[str] = None,
1190
+ filename: Optional[str] = None,
1075
1191
  **kwargs,
1076
1192
  ) -> requests.Response:
1077
1193
  """Upload file to server from bytes.
@@ -1087,6 +1203,8 @@ def upload_file_from_stream(
1087
1203
  to track upload progress.
1088
1204
  request_type (Optional[RequestType]): Type of request that will
1089
1205
  be used to upload file.
1206
+ content_type (Optional[str]): MIME type of the file.
1207
+ filename (Optional[str]): Filename of file on server.
1090
1208
  **kwargs (Any): Additional arguments that will be passed
1091
1209
  to request function.
1092
1210
 
@@ -1100,6 +1218,8 @@ def upload_file_from_stream(
1100
1218
  stream=stream,
1101
1219
  progress=progress,
1102
1220
  request_type=request_type,
1221
+ content_type=content_type,
1222
+ filename=filename,
1103
1223
  **kwargs,
1104
1224
  )
1105
1225
 
@@ -1109,6 +1229,9 @@ def upload_file(
1109
1229
  filepath: str,
1110
1230
  progress: Optional[TransferProgress] = None,
1111
1231
  request_type: Optional[RequestType] = None,
1232
+ *,
1233
+ content_type: Optional[str] = None,
1234
+ filename: Optional[str] = None,
1112
1235
  **kwargs,
1113
1236
  ) -> requests.Response:
1114
1237
  """Upload file to server.
@@ -1124,6 +1247,8 @@ def upload_file(
1124
1247
  to track upload progress.
1125
1248
  request_type (Optional[RequestType]): Type of request that will
1126
1249
  be used to upload file.
1250
+ content_type (Optional[str]): MIME type of the file.
1251
+ filename (Optional[str]): Filename of file on server.
1127
1252
  **kwargs (Any): Additional arguments that will be passed
1128
1253
  to request function.
1129
1254
 
@@ -1137,6 +1262,8 @@ def upload_file(
1137
1262
  filepath=filepath,
1138
1263
  progress=progress,
1139
1264
  request_type=request_type,
1265
+ content_type=content_type,
1266
+ filename=filename,
1140
1267
  **kwargs,
1141
1268
  )
1142
1269
 
@@ -2889,6 +3016,7 @@ def get_addon_site_settings(
2889
3016
  def get_bundle_settings(
2890
3017
  bundle_name: Optional[str] = None,
2891
3018
  project_name: Optional[str] = None,
3019
+ project_bundle_name: Optional[str] = None,
2892
3020
  variant: Optional[str] = None,
2893
3021
  site_id: Optional[str] = None,
2894
3022
  use_site: bool = True,
@@ -2928,6 +3056,7 @@ def get_bundle_settings(
2928
3056
  return con.get_bundle_settings(
2929
3057
  bundle_name=bundle_name,
2930
3058
  project_name=project_name,
3059
+ project_bundle_name=project_bundle_name,
2931
3060
  variant=variant,
2932
3061
  site_id=site_id,
2933
3062
  use_site=use_site,
@@ -2936,6 +3065,7 @@ def get_bundle_settings(
2936
3065
 
2937
3066
  def get_addons_studio_settings(
2938
3067
  bundle_name: Optional[str] = None,
3068
+ project_bundle_name: Optional[str] = None,
2939
3069
  variant: Optional[str] = None,
2940
3070
  site_id: Optional[str] = None,
2941
3071
  use_site: bool = True,
@@ -2951,6 +3081,8 @@ def get_addons_studio_settings(
2951
3081
  Args:
2952
3082
  bundle_name (Optional[str]): Name of bundle for which should be
2953
3083
  settings received.
3084
+ project_bundle_name (Optional[str]): Project bundle name for
3085
+ which should be settings received.
2954
3086
  variant (Optional[Literal['production', 'staging']]): Name of
2955
3087
  settings variant. Used 'default_settings_variant' by default.
2956
3088
  site_id (Optional[str]): Site id for which want to receive
@@ -2968,6 +3100,7 @@ def get_addons_studio_settings(
2968
3100
  con = get_server_api_connection()
2969
3101
  return con.get_addons_studio_settings(
2970
3102
  bundle_name=bundle_name,
3103
+ project_bundle_name=project_bundle_name,
2971
3104
  variant=variant,
2972
3105
  site_id=site_id,
2973
3106
  use_site=use_site,
@@ -2978,6 +3111,7 @@ def get_addons_studio_settings(
2978
3111
  def get_addons_project_settings(
2979
3112
  project_name: str,
2980
3113
  bundle_name: Optional[str] = None,
3114
+ project_bundle_name: Optional[str] = None,
2981
3115
  variant: Optional[str] = None,
2982
3116
  site_id: Optional[str] = None,
2983
3117
  use_site: bool = True,
@@ -3009,6 +3143,8 @@ def get_addons_project_settings(
3009
3143
  received.
3010
3144
  bundle_name (Optional[str]): Name of bundle for which should be
3011
3145
  settings received.
3146
+ project_bundle_name (Optional[str]): Project bundle name for which
3147
+ should be settings received.
3012
3148
  variant (Optional[Literal['production', 'staging']]): Name of
3013
3149
  settings variant. Used 'default_settings_variant' by default.
3014
3150
  site_id (Optional[str]): Site id for which want to receive
@@ -3028,6 +3164,7 @@ def get_addons_project_settings(
3028
3164
  return con.get_addons_project_settings(
3029
3165
  project_name=project_name,
3030
3166
  bundle_name=bundle_name,
3167
+ project_bundle_name=project_bundle_name,
3031
3168
  variant=variant,
3032
3169
  site_id=site_id,
3033
3170
  use_site=use_site,
@@ -3037,6 +3174,7 @@ def get_addons_project_settings(
3037
3174
 
3038
3175
  def get_addons_settings(
3039
3176
  bundle_name: Optional[str] = None,
3177
+ project_bundle_name: Optional[str] = None,
3040
3178
  project_name: Optional[str] = None,
3041
3179
  variant: Optional[str] = None,
3042
3180
  site_id: Optional[str] = None,
@@ -3056,6 +3194,8 @@ def get_addons_settings(
3056
3194
  Args:
3057
3195
  bundle_name (Optional[str]): Name of bundle for which should be
3058
3196
  settings received.
3197
+ project_bundle_name (Optional[str]): Name of project bundle
3198
+ for which should be settings received.
3059
3199
  project_name (Optional[str]): Name of project for which should be
3060
3200
  settings received.
3061
3201
  variant (Optional[Literal['production', 'staging']]): Name of
@@ -3072,6 +3212,7 @@ def get_addons_settings(
3072
3212
  con = get_server_api_connection()
3073
3213
  return con.get_addons_settings(
3074
3214
  bundle_name=bundle_name,
3215
+ project_bundle_name=project_bundle_name,
3075
3216
  project_name=project_name,
3076
3217
  variant=variant,
3077
3218
  site_id=site_id,
@@ -6887,6 +7028,7 @@ def create_link(
6887
7028
  output_id: str,
6888
7029
  output_type: str,
6889
7030
  link_name: Optional[str] = None,
7031
+ data: Optional[dict[str, Any]] = None,
6890
7032
  ) -> CreateLinkData:
6891
7033
  """Create link between 2 entities.
6892
7034
 
@@ -6906,7 +7048,8 @@ def create_link(
6906
7048
  output_id (str): Output entity id.
6907
7049
  output_type (str): Entity type of output entity.
6908
7050
  link_name (Optional[str]): Name of link.
6909
- Available from server version '1.0.0-rc.6'.
7051
+ data (Optional[dict[str, Any]]): Additional data to be stored
7052
+ with the link.
6910
7053
 
6911
7054
  Returns:
6912
7055
  CreateLinkData: Information about link.
@@ -6924,6 +7067,7 @@ def create_link(
6924
7067
  output_id=output_id,
6925
7068
  output_type=output_type,
6926
7069
  link_name=link_name,
7070
+ data=data,
6927
7071
  )
6928
7072
 
6929
7073
 
@@ -28,6 +28,9 @@ class BaseServerAPI:
28
28
  def is_product_base_type_supported(self) -> bool:
29
29
  raise NotImplementedError()
30
30
 
31
+ def links_graphql_support_data(self) -> bool:
32
+ raise NotImplementedError()
33
+
31
34
  def get_server_version(self) -> str:
32
35
  raise NotImplementedError()
33
36
 
@@ -142,6 +145,9 @@ class BaseServerAPI:
142
145
  ):
143
146
  raise NotImplementedError()
144
147
 
148
+ def _prepare_link_fields(self, fields: set[str]) -> None:
149
+ raise NotImplementedError()
150
+
145
151
  def _prepare_advanced_filters(
146
152
  self, filters: Union[str, dict[str, Any], None]
147
153
  ) -> Optional[str]:
@@ -671,6 +671,7 @@ class BundlesAddonsAPI(BaseServerAPI):
671
671
  self,
672
672
  bundle_name: Optional[str] = None,
673
673
  project_name: Optional[str] = None,
674
+ project_bundle_name: Optional[str] = None,
674
675
  variant: Optional[str] = None,
675
676
  site_id: Optional[str] = None,
676
677
  use_site: bool = True,
@@ -714,6 +715,7 @@ class BundlesAddonsAPI(BaseServerAPI):
714
715
  query = prepare_query_string({
715
716
  "project_name": project_name or None,
716
717
  "bundle_name": bundle_name or None,
718
+ "project_bundle_name": project_bundle_name or None,
717
719
  "variant": variant or self.get_default_settings_variant() or None,
718
720
  "site_id": site_id,
719
721
  })
@@ -724,6 +726,7 @@ class BundlesAddonsAPI(BaseServerAPI):
724
726
  def get_addons_studio_settings(
725
727
  self,
726
728
  bundle_name: Optional[str] = None,
729
+ project_bundle_name: Optional[str] = None,
727
730
  variant: Optional[str] = None,
728
731
  site_id: Optional[str] = None,
729
732
  use_site: bool = True,
@@ -739,6 +742,8 @@ class BundlesAddonsAPI(BaseServerAPI):
739
742
  Args:
740
743
  bundle_name (Optional[str]): Name of bundle for which should be
741
744
  settings received.
745
+ project_bundle_name (Optional[str]): Project bundle name for
746
+ which should be settings received.
742
747
  variant (Optional[Literal['production', 'staging']]): Name of
743
748
  settings variant. Used 'default_settings_variant' by default.
744
749
  site_id (Optional[str]): Site id for which want to receive
@@ -755,6 +760,7 @@ class BundlesAddonsAPI(BaseServerAPI):
755
760
  """
756
761
  output = self.get_bundle_settings(
757
762
  bundle_name=bundle_name,
763
+ project_bundle_name=project_bundle_name,
758
764
  variant=variant,
759
765
  site_id=site_id,
760
766
  use_site=use_site
@@ -770,6 +776,7 @@ class BundlesAddonsAPI(BaseServerAPI):
770
776
  self,
771
777
  project_name: str,
772
778
  bundle_name: Optional[str] = None,
779
+ project_bundle_name: Optional[str] = None,
773
780
  variant: Optional[str] = None,
774
781
  site_id: Optional[str] = None,
775
782
  use_site: bool = True,
@@ -801,6 +808,8 @@ class BundlesAddonsAPI(BaseServerAPI):
801
808
  received.
802
809
  bundle_name (Optional[str]): Name of bundle for which should be
803
810
  settings received.
811
+ project_bundle_name (Optional[str]): Project bundle name for which
812
+ should be settings received.
804
813
  variant (Optional[Literal['production', 'staging']]): Name of
805
814
  settings variant. Used 'default_settings_variant' by default.
806
815
  site_id (Optional[str]): Site id for which want to receive
@@ -822,6 +831,7 @@ class BundlesAddonsAPI(BaseServerAPI):
822
831
  output = self.get_bundle_settings(
823
832
  project_name=project_name,
824
833
  bundle_name=bundle_name,
834
+ project_bundle_name=project_bundle_name,
825
835
  variant=variant,
826
836
  site_id=site_id,
827
837
  use_site=use_site
@@ -836,6 +846,7 @@ class BundlesAddonsAPI(BaseServerAPI):
836
846
  def get_addons_settings(
837
847
  self,
838
848
  bundle_name: Optional[str] = None,
849
+ project_bundle_name: Optional[str] = None,
839
850
  project_name: Optional[str] = None,
840
851
  variant: Optional[str] = None,
841
852
  site_id: Optional[str] = None,
@@ -855,6 +866,8 @@ class BundlesAddonsAPI(BaseServerAPI):
855
866
  Args:
856
867
  bundle_name (Optional[str]): Name of bundle for which should be
857
868
  settings received.
869
+ project_bundle_name (Optional[str]): Name of project bundle
870
+ for which should be settings received.
858
871
  project_name (Optional[str]): Name of project for which should be
859
872
  settings received.
860
873
  variant (Optional[Literal['production', 'staging']]): Name of
@@ -871,6 +884,7 @@ class BundlesAddonsAPI(BaseServerAPI):
871
884
  if project_name is None:
872
885
  return self.get_addons_studio_settings(
873
886
  bundle_name=bundle_name,
887
+ project_bundle_name=project_bundle_name,
874
888
  variant=variant,
875
889
  site_id=site_id,
876
890
  use_site=use_site,
@@ -880,6 +894,7 @@ class BundlesAddonsAPI(BaseServerAPI):
880
894
  return self.get_addons_project_settings(
881
895
  project_name=project_name,
882
896
  bundle_name=bundle_name,
897
+ project_bundle_name=project_bundle_name,
883
898
  variant=variant,
884
899
  site_id=site_id,
885
900
  use_site=use_site,
@@ -331,6 +331,8 @@ class FoldersAPI(BaseServerAPI):
331
331
  if own_attributes:
332
332
  fields.add("ownAttrib")
333
333
 
334
+ self._prepare_link_fields(fields)
335
+
334
336
  query = folders_graphql_query(fields)
335
337
  for attr, filter_value in graphql_filters.items():
336
338
  query.set_variable_value(attr, filter_value)
@@ -362,6 +362,7 @@ class LinksAPI(BaseServerAPI):
362
362
  return output
363
363
 
364
364
  link_fields = {"id", "links"}
365
+ self._prepare_link_fields(link_fields)
365
366
  query = query_func(link_fields)
366
367
  for attr, filter_value in filters.items():
367
368
  query.set_variable_value(attr, filter_value)
@@ -179,6 +179,8 @@ class ProductsAPI(BaseServerAPI):
179
179
  if filter_value:
180
180
  graphql_filters[filter_key] = filter_value
181
181
 
182
+ self._prepare_link_fields(fields)
183
+
182
184
  query = products_graphql_query(fields)
183
185
  for attr, filter_value in graphql_filters.items():
184
186
  query.set_variable_value(attr, filter_value)
@@ -108,6 +108,8 @@ class RepresentationsAPI(BaseServerAPI):
108
108
  fields.discard("files")
109
109
  fields |= REPRESENTATION_FILES_FIELDS
110
110
 
111
+ self._prepare_link_fields(fields)
112
+
111
113
  graphql_filters = {
112
114
  "projectName": project_name
113
115
  }
@@ -106,6 +106,8 @@ class TasksAPI(BaseServerAPI):
106
106
  if active is not None:
107
107
  fields.add("active")
108
108
 
109
+ self._prepare_link_fields(fields)
110
+
109
111
  query = tasks_graphql_query(fields)
110
112
  for attr, filter_value in graphql_filters.items():
111
113
  query.set_variable_value(attr, filter_value)
@@ -267,6 +269,8 @@ class TasksAPI(BaseServerAPI):
267
269
  if active is not None:
268
270
  fields.add("active")
269
271
 
272
+ self._prepare_link_fields(fields)
273
+
270
274
  query = tasks_by_folder_paths_graphql_query(fields)
271
275
  for attr, filter_value in graphql_filters.items():
272
276
  query.set_variable_value(attr, filter_value)
@@ -87,6 +87,8 @@ class VersionsAPI(BaseServerAPI):
87
87
  if active is not None:
88
88
  fields.add("active")
89
89
 
90
+ self._prepare_link_fields(fields)
91
+
90
92
  if own_attributes is not _PLACEHOLDER:
91
93
  warnings.warn(
92
94
  (
@@ -116,7 +118,6 @@ class VersionsAPI(BaseServerAPI):
116
118
  ):
117
119
  return
118
120
 
119
-
120
121
  filters = self._prepare_advanced_filters(filters)
121
122
  if filters:
122
123
  graphql_filters["filter"] = filters
@@ -93,6 +93,8 @@ class WorkfilesAPI(BaseServerAPI):
93
93
  fields = set(fields)
94
94
  self._prepare_fields("workfile", fields)
95
95
 
96
+ self._prepare_link_fields(fields)
97
+
96
98
  query = workfiles_info_graphql_query(fields)
97
99
 
98
100
  for attr, filter_value in filters.items():
@@ -1,18 +1,31 @@
1
+ from __future__ import annotations
2
+
1
3
  import collections
4
+ import typing
2
5
 
3
6
  from .constants import DEFAULT_LINK_FIELDS
4
7
  from .graphql import FIELD_VALUE, GraphQlQuery, fields_to_dict
5
8
 
9
+ if typing.TYPE_CHECKING:
10
+ from .graphql import (
11
+ GraphQlQueryEdgeField,
12
+ )
13
+
6
14
 
7
- def add_links_fields(entity_field, nested_fields):
15
+ def add_links_fields(
16
+ entity_field: GraphQlQueryEdgeField,
17
+ nested_fields: dict | None,
18
+ ) -> None:
8
19
  if "links" not in nested_fields:
9
20
  return
10
21
  links_fields = nested_fields.pop("links")
11
-
12
22
  link_edge_fields = set(DEFAULT_LINK_FIELDS)
23
+
13
24
  if isinstance(links_fields, dict):
14
25
  simple_fields = set(links_fields)
15
- simple_variant = len(simple_fields - link_edge_fields) == 0
26
+ diff = simple_fields - link_edge_fields
27
+ diff.discard("data")
28
+ simple_variant = len(diff) == 0
16
29
  else:
17
30
  simple_variant = True
18
31
  simple_fields = link_edge_fields
@@ -121,7 +134,7 @@ def product_types_query(fields):
121
134
  return query
122
135
 
123
136
 
124
- def folders_graphql_query(fields):
137
+ def folders_graphql_query(fields: set[str]) -> GraphQlQuery:
125
138
  query = GraphQlQuery("FoldersQuery")
126
139
  project_name_var = query.add_variable("projectName", "String!")
127
140
  folder_ids_var = query.add_variable("folderIds", "[String!]")
@@ -161,6 +174,7 @@ def folders_graphql_query(fields):
161
174
  folders_field.set_filter("filter", filter_var)
162
175
 
163
176
  nested_fields = fields_to_dict(fields)
177
+
164
178
  add_links_fields(folders_field, nested_fields)
165
179
 
166
180
  query_queue = collections.deque()
@@ -179,7 +193,7 @@ def folders_graphql_query(fields):
179
193
  return query
180
194
 
181
195
 
182
- def tasks_graphql_query(fields):
196
+ def tasks_graphql_query(fields: set[str]) -> GraphQlQuery:
183
197
  query = GraphQlQuery("TasksQuery")
184
198
  project_name_var = query.add_variable("projectName", "String!")
185
199
  task_ids_var = query.add_variable("taskIds", "[String!]")
@@ -227,7 +241,7 @@ def tasks_graphql_query(fields):
227
241
  return query
228
242
 
229
243
 
230
- def tasks_by_folder_paths_graphql_query(fields):
244
+ def tasks_by_folder_paths_graphql_query(fields: set[str]) -> GraphQlQuery:
231
245
  query = GraphQlQuery("TasksByFolderPathQuery")
232
246
  project_name_var = query.add_variable("projectName", "String!")
233
247
  task_names_var = query.add_variable("taskNames", "[String!]")
@@ -258,6 +272,7 @@ def tasks_by_folder_paths_graphql_query(fields):
258
272
  tasks_field.set_filter("filter", filter_var)
259
273
 
260
274
  nested_fields = fields_to_dict(fields)
275
+
261
276
  add_links_fields(tasks_field, nested_fields)
262
277
 
263
278
  query_queue = collections.deque()
@@ -276,7 +291,7 @@ def tasks_by_folder_paths_graphql_query(fields):
276
291
  return query
277
292
 
278
293
 
279
- def products_graphql_query(fields):
294
+ def products_graphql_query(fields: set[str]) -> GraphQlQuery:
280
295
  query = GraphQlQuery("ProductsQuery")
281
296
 
282
297
  project_name_var = query.add_variable("projectName", "String!")
@@ -326,7 +341,7 @@ def products_graphql_query(fields):
326
341
  return query
327
342
 
328
343
 
329
- def versions_graphql_query(fields):
344
+ def versions_graphql_query(fields: set[str]) -> GraphQlQuery:
330
345
  query = GraphQlQuery("VersionsQuery")
331
346
 
332
347
  project_name_var = query.add_variable("projectName", "String!")
@@ -377,7 +392,7 @@ def versions_graphql_query(fields):
377
392
  return query
378
393
 
379
394
 
380
- def representations_graphql_query(fields):
395
+ def representations_graphql_query(fields: set[str]) -> GraphQlQuery:
381
396
  query = GraphQlQuery("RepresentationsQuery")
382
397
 
383
398
  project_name_var = query.add_variable("projectName", "String!")
@@ -525,7 +540,7 @@ def representations_hierarchy_qraphql_query(
525
540
  return query
526
541
 
527
542
 
528
- def workfiles_info_graphql_query(fields):
543
+ def workfiles_info_graphql_query(fields: set[str]) -> GraphQlQuery:
529
544
  query = GraphQlQuery("WorkfilesInfo")
530
545
  project_name_var = query.add_variable("projectName", "String!")
531
546
  workfiles_info_ids = query.add_variable("workfileIds", "[String!]")
@@ -834,8 +834,8 @@ class OperationsSession(object):
834
834
  if body is not None:
835
835
  operations_body.append(body)
836
836
 
837
- self._con.send_batch_operations(
838
- project_name, operations_body, can_fail=False
837
+ self._con.send_background_batch_operations(
838
+ project_name, operations_body, wait=True, can_fail=False
839
839
  )
840
840
 
841
841
  def create_entity(
@@ -38,6 +38,7 @@ from .constants import (
38
38
  DEFAULT_ACTIVITY_FIELDS,
39
39
  DEFAULT_USER_FIELDS,
40
40
  DEFAULT_ENTITY_LIST_FIELDS,
41
+ DEFAULT_LINK_FIELDS,
41
42
  )
42
43
  from .graphql import INTROSPECTION_QUERY
43
44
  from .graphql_queries import users_graphql_query
@@ -62,6 +63,7 @@ from .utils import (
62
63
  get_default_site_id,
63
64
  NOT_SET,
64
65
  get_media_mime_type,
66
+ get_media_mime_type_for_stream,
65
67
  get_machine_name,
66
68
  fill_own_attribs,
67
69
  )
@@ -328,6 +330,7 @@ class ServerAPI(
328
330
 
329
331
  self._graphql_allows_traits_in_representations: Optional[bool] = None
330
332
  self._product_base_type_supported = None
333
+ self._links_graphql_support_data = None
331
334
 
332
335
  self._session = None
333
336
 
@@ -921,6 +924,15 @@ class ServerAPI(
921
924
  )
922
925
  return self._product_base_type_supported
923
926
 
927
+ def links_graphql_support_data(self) -> bool:
928
+ """Links data can be received by GraphQl."""
929
+ if self._links_graphql_support_data is None:
930
+ major, minor, patch, _, _ = self.server_version_tuple
931
+ self._links_graphql_support_data = (
932
+ (major, minor, patch) >= (1, 14, 2)
933
+ )
934
+ return self._links_graphql_support_data
935
+
924
936
  def _get_user_info(self) -> Optional[dict[str, Any]]:
925
937
  if self._access_token is None:
926
938
  return None
@@ -1414,7 +1426,7 @@ class ServerAPI(
1414
1426
  if api_prepended:
1415
1427
  self.log.warning(
1416
1428
  f"Auto-fixed endpoint '{endpoint}' -> 'api/{endpoint}'."
1417
- " Please fix the endpoit passed to the function."
1429
+ " Please fix the endpoint passed to the function."
1418
1430
  )
1419
1431
 
1420
1432
  def download_file_to_stream(
@@ -1517,6 +1529,117 @@ class ServerAPI(
1517
1529
 
1518
1530
  return progress
1519
1531
 
1532
+ def upload_project_file(
1533
+ self,
1534
+ project_name: str,
1535
+ filepath: str,
1536
+ *,
1537
+ content_type: Optional[str] = None,
1538
+ filename: Optional[str] = None,
1539
+ file_id: Optional[str] = None,
1540
+ activity_id: Optional[str] = None,
1541
+ chunk_size: Optional[int] = None,
1542
+ progress: Optional[TransferProgress] = None,
1543
+ ) -> requests.Response:
1544
+ """Upload project file from a filepath.
1545
+
1546
+ Project files are usually binary files, such as images, videos,
1547
+ or other media files that can be accessed via api endpoint
1548
+ '{server url}/api/projects/{project_name}/files/{file_id}'.
1549
+
1550
+ Args:
1551
+ project_name (str): Project name.
1552
+ filepath (str): Path where file will be downloaded.
1553
+ content_type (Optional[str]): MIME type of file.
1554
+ filename (Optional[str]): Server filename, filename from filepath
1555
+ is used if not passed.
1556
+ file_id (Optional[str]): File id.
1557
+ activity_id (Optional[str]): To which activity is file related.
1558
+ chunk_size (Optional[int]): Size of chunks that are received
1559
+ in single loop.
1560
+ progress (Optional[TransferProgress]): Object that gives ability
1561
+ to track download progress.
1562
+
1563
+ Returns:
1564
+ requests.Response: Requests response.
1565
+
1566
+ """
1567
+ if not filename:
1568
+ filename = os.path.basename(filepath)
1569
+
1570
+ if not content_type:
1571
+ content_type = get_media_mime_type(filepath)
1572
+ if not content_type:
1573
+ content_type = "application/octet-stream"
1574
+
1575
+ query = prepare_query_string({
1576
+ "x_file_id": file_id,
1577
+ "x_activity_id": activity_id,
1578
+ })
1579
+ return self.upload_file(
1580
+ f"api/projects/{project_name}/files{query}",
1581
+ filepath,
1582
+ content_type=content_type,
1583
+ filename=filename,
1584
+ chunk_size=chunk_size,
1585
+ progress=progress,
1586
+ request_type=RequestTypes.post,
1587
+ )
1588
+
1589
+ def upload_project_file_from_stream(
1590
+ self,
1591
+ project_name: str,
1592
+ stream: StreamType,
1593
+ filename: str,
1594
+ *,
1595
+ content_type: Optional[str] = None,
1596
+ file_id: Optional[str] = None,
1597
+ activity_id: Optional[str] = None,
1598
+ chunk_size: Optional[int] = None,
1599
+ progress: Optional[TransferProgress] = None,
1600
+ ) -> requests.Response:
1601
+ """Upload project file from a filepath.
1602
+
1603
+ Project files are usually binary files, such as images, videos,
1604
+ or other media files that can be accessed via api endpoint
1605
+ '{server url}/api/projects/{project_name}/files/{file_id}'.
1606
+
1607
+ Args:
1608
+ project_name (str): Project name.
1609
+ stream (StreamType): Stream used as source for upload.
1610
+ filename (str): Name of file on server.
1611
+ content_type (Optional[str]): MIME type of file.
1612
+ file_id (Optional[str]): File id.
1613
+ activity_id (Optional[str]): To which activity is file related.
1614
+ chunk_size (Optional[int]): Size of chunks that are received
1615
+ in single loop.
1616
+ progress (Optional[TransferProgress]): Object that gives ability
1617
+ to track download progress.
1618
+
1619
+ Returns:
1620
+ requests.Response: Requests response.
1621
+
1622
+ """
1623
+ if not content_type:
1624
+ stream.seek(0)
1625
+ content_type = get_media_mime_type_for_stream(stream)
1626
+ if not content_type:
1627
+ content_type = "application/octet-stream"
1628
+
1629
+ query = prepare_query_string({
1630
+ "x_file_id": file_id,
1631
+ "x_activity_id": activity_id,
1632
+ })
1633
+ return self.upload_file_from_stream(
1634
+ f"api/projects/{project_name}/files{query}",
1635
+ stream,
1636
+ content_type=content_type,
1637
+ filename=filename,
1638
+ chunk_size=chunk_size,
1639
+ progress=progress,
1640
+ request_type=RequestTypes.post,
1641
+ )
1642
+
1520
1643
  def download_project_file(
1521
1644
  self,
1522
1645
  project_name: str,
@@ -1587,6 +1710,11 @@ class ServerAPI(
1587
1710
  progress=progress,
1588
1711
  )
1589
1712
 
1713
+ def delete_project_file(self, project_name: str, file_id: str) -> None:
1714
+ """Delete project file."""
1715
+ response = self.delete(f"projects/{project_name}/files/{file_id}")
1716
+ response.raise_for_status()
1717
+
1590
1718
  @staticmethod
1591
1719
  def _upload_chunks_iter(
1592
1720
  file_stream: StreamType,
@@ -1619,6 +1747,9 @@ class ServerAPI(
1619
1747
  progress: TransferProgress,
1620
1748
  request_type: Optional[RequestType] = None,
1621
1749
  chunk_size: Optional[int] = None,
1750
+ *,
1751
+ content_type: Optional[str] = None,
1752
+ filename: Optional[str] = None,
1622
1753
  **kwargs
1623
1754
  ) -> requests.Response:
1624
1755
  """Upload file to server.
@@ -1646,11 +1777,14 @@ class ServerAPI(
1646
1777
  url = self._endpoint_to_url(endpoint, use_rest=False)
1647
1778
  progress.set_destination_url(url)
1648
1779
 
1780
+ headers = kwargs.setdefault("headers", {})
1781
+ headers_keys_by_low_key = {key.lower(): key for key in headers}
1649
1782
  if self._session is None:
1650
- headers = kwargs.setdefault("headers", {})
1651
1783
  for key, value in self.get_headers().items():
1652
- if key not in headers:
1784
+ orig_key = headers_keys_by_low_key.get(key)
1785
+ if not orig_key:
1653
1786
  headers[key] = value
1787
+
1654
1788
  post_func = self._base_functions_mapping[request_type]
1655
1789
  else:
1656
1790
  post_func = self._session_functions_mapping[request_type]
@@ -1658,6 +1792,17 @@ class ServerAPI(
1658
1792
  if not chunk_size:
1659
1793
  chunk_size = self.default_upload_chunk_size
1660
1794
 
1795
+ for key, value in (
1796
+ ("x-file-name", filename),
1797
+ ("Content-Type", content_type),
1798
+ ):
1799
+ if not value:
1800
+ continue
1801
+ orig_key = headers_keys_by_low_key.get(key.lower())
1802
+ if orig_key:
1803
+ headers.pop(orig_key)
1804
+ headers[key] = value
1805
+
1661
1806
  retries = self.get_default_max_retries()
1662
1807
  response = None
1663
1808
 
@@ -1702,7 +1847,7 @@ class ServerAPI(
1702
1847
  if api_prepended:
1703
1848
  self.log.warning(
1704
1849
  f"Auto-fixed endpoint '{endpoint}' -> 'api/{endpoint}'."
1705
- " Please fix the endpoit passed to the function."
1850
+ " Please fix the endpoint passed to the function."
1706
1851
  )
1707
1852
  return response
1708
1853
 
@@ -1712,6 +1857,9 @@ class ServerAPI(
1712
1857
  stream: StreamType,
1713
1858
  progress: Optional[TransferProgress] = None,
1714
1859
  request_type: Optional[RequestType] = None,
1860
+ *,
1861
+ content_type: Optional[str] = None,
1862
+ filename: Optional[str] = None,
1715
1863
  **kwargs
1716
1864
  ) -> requests.Response:
1717
1865
  """Upload file to server from bytes.
@@ -1727,6 +1875,8 @@ class ServerAPI(
1727
1875
  to track upload progress.
1728
1876
  request_type (Optional[RequestType]): Type of request that will
1729
1877
  be used to upload file.
1878
+ content_type (Optional[str]): MIME type of the file.
1879
+ filename (Optional[str]): Filename of file on server.
1730
1880
  **kwargs (Any): Additional arguments that will be passed
1731
1881
  to request function.
1732
1882
 
@@ -1747,6 +1897,8 @@ class ServerAPI(
1747
1897
  stream,
1748
1898
  progress,
1749
1899
  request_type,
1900
+ content_type=content_type,
1901
+ filename=filename,
1750
1902
  **kwargs
1751
1903
  )
1752
1904
 
@@ -1763,6 +1915,9 @@ class ServerAPI(
1763
1915
  filepath: str,
1764
1916
  progress: Optional[TransferProgress] = None,
1765
1917
  request_type: Optional[RequestType] = None,
1918
+ *,
1919
+ content_type: Optional[str] = None,
1920
+ filename: Optional[str] = None,
1766
1921
  **kwargs
1767
1922
  ) -> requests.Response:
1768
1923
  """Upload file to server.
@@ -1778,6 +1933,8 @@ class ServerAPI(
1778
1933
  to track upload progress.
1779
1934
  request_type (Optional[RequestType]): Type of request that will
1780
1935
  be used to upload file.
1936
+ content_type (Optional[str]): MIME type of the file.
1937
+ filename (Optional[str]): Filename of file on server.
1781
1938
  **kwargs (Any): Additional arguments that will be passed
1782
1939
  to request function.
1783
1940
 
@@ -1796,6 +1953,8 @@ class ServerAPI(
1796
1953
  stream,
1797
1954
  progress,
1798
1955
  request_type,
1956
+ content_type=content_type,
1957
+ filename=filename,
1799
1958
  **kwargs
1800
1959
  )
1801
1960
 
@@ -1837,24 +1996,9 @@ class ServerAPI(
1837
1996
  f"Could not determine MIME type of file '{filepath}'"
1838
1997
  )
1839
1998
 
1840
- if headers is None:
1841
- headers = self.get_headers(content_type)
1842
- else:
1843
- # Make sure content-type is filled with file content type
1844
- content_type_key = next(
1845
- (
1846
- key
1847
- for key in headers
1848
- if key.lower() == "content-type"
1849
- ),
1850
- "Content-Type"
1851
- )
1852
- headers[content_type_key] = content_type
1853
-
1854
1999
  # Fill original filename if not explicitly defined
1855
2000
  if not filename:
1856
2001
  filename = os.path.basename(filepath)
1857
- headers["x-file-name"] = filename
1858
2002
 
1859
2003
  query = prepare_query_string({"label": label or None})
1860
2004
  endpoint = (
@@ -1865,6 +2009,8 @@ class ServerAPI(
1865
2009
  endpoint,
1866
2010
  filepath,
1867
2011
  progress=progress,
2012
+ content_type=content_type,
2013
+ filename=filename,
1868
2014
  headers=headers,
1869
2015
  request_type=RequestTypes.post,
1870
2016
  **kwargs
@@ -2310,6 +2456,17 @@ class ServerAPI(
2310
2456
  )
2311
2457
  }
2312
2458
 
2459
+ def _prepare_link_fields(self, fields: set[str]) -> None:
2460
+ if "links" not in fields:
2461
+ return
2462
+
2463
+ fields.discard("links")
2464
+ for field in DEFAULT_LINK_FIELDS:
2465
+ fields.add(f"links.{field}")
2466
+
2467
+ if self.links_graphql_support_data():
2468
+ fields.add("links.data")
2469
+
2313
2470
  def _prepare_advanced_filters(
2314
2471
  self, filters: Union[str, dict[str, Any], None]
2315
2472
  ) -> Optional[str]:
@@ -1118,12 +1118,12 @@ def _get_media_mime_type_for_content_base(content: bytes) -> Optional[str]:
1118
1118
  content_len = len(content)
1119
1119
  # Pre-validation (largest definition check)
1120
1120
  # - hopefully there cannot be media defined in less than 12 bytes
1121
- if content_len < 12:
1121
+ if content_len < 4:
1122
1122
  return None
1123
1123
 
1124
- # FTYP
1125
- if content[4:8] == b"ftyp":
1126
- return _get_media_mime_type_from_ftyp(content)
1124
+ # PDF
1125
+ if content[0:4] == b"%PDF":
1126
+ return "application/pdf"
1127
1127
 
1128
1128
  # BMP
1129
1129
  if content[0:2] == b"BM":
@@ -1162,6 +1162,14 @@ def _get_media_mime_type_for_content_base(content: bytes) -> Optional[str]:
1162
1162
  # with this header
1163
1163
  if content[0:4] == b"\x00\x00\x01\x00":
1164
1164
  return "image/x-icon"
1165
+
1166
+ if content_len < 8:
1167
+ return None
1168
+
1169
+ # FTYP
1170
+ if content[4:8] == b"ftyp":
1171
+ return _get_media_mime_type_from_ftyp(content)
1172
+
1165
1173
  return None
1166
1174
 
1167
1175
 
@@ -1172,23 +1180,32 @@ def _get_svg_mime_type(content: bytes) -> Optional[str]:
1172
1180
  return None
1173
1181
 
1174
1182
 
1183
+ def _get_json_mime_type(content: bytes) -> Optional[str]:
1184
+ # json
1185
+ try:
1186
+ json.loads(content.decode("utf-8"))
1187
+ return "application/json"
1188
+ except (UnicodeDecodeError, ValueError):
1189
+ pass
1190
+ return None
1191
+
1192
+
1175
1193
  def get_media_mime_type_for_content(content: bytes) -> Optional[str]:
1176
1194
  mime_type = _get_media_mime_type_for_content_base(content)
1177
1195
  if mime_type is not None:
1178
1196
  return mime_type
1179
- return _get_svg_mime_type(content)
1197
+ return _get_svg_mime_type(content) or _get_json_mime_type(content)
1180
1198
 
1181
1199
 
1182
1200
  def get_media_mime_type_for_stream(stream: StreamType) -> Optional[str]:
1183
1201
  # Read only 12 bytes to determine mime type
1184
1202
  content = stream.read(12)
1185
- if len(content) < 12:
1186
- return None
1187
1203
  mime_type = _get_media_mime_type_for_content_base(content)
1188
- if mime_type is None:
1189
- content += stream.read()
1190
- mime_type = _get_svg_mime_type(content)
1191
- return mime_type
1204
+ if mime_type is not None:
1205
+ return mime_type
1206
+
1207
+ content += stream.read()
1208
+ return _get_svg_mime_type(content) or _get_json_mime_type(content)
1192
1209
 
1193
1210
 
1194
1211
  def get_media_mime_type(filepath: str) -> Optional[str]:
@@ -1,2 +1,2 @@
1
1
  """Package declaring Python API for AYON server."""
2
- __version__ = "1.2.11"
2
+ __version__ = "1.2.13"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ayon_python_api
3
- Version: 1.2.11
3
+ Version: 1.2.13
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.2.11"
3
+ version = "1.2.13"
4
4
  description = "AYON Python API"
5
5
  license = {file = "LICENSE"}
6
6
  readme = {file = "README.md", content-type = "text/markdown"}
@@ -28,7 +28,7 @@ build-backend = "poetry.core.masonry.api"
28
28
 
29
29
  [tool.poetry]
30
30
  name = "ayon_python_api"
31
- version = "1.2.2"
31
+ version = "1.2.13"
32
32
  description = "AYON Python API"
33
33
  authors = [
34
34
  "ynput.io <info@ynput.io>"
@@ -49,4 +49,4 @@ mock = "*"
49
49
  sphinx-autoapi = "*"
50
50
  revitron-sphinx-theme = { git = "https://github.com/revitron/revitron-sphinx-theme.git", branch = "master" }
51
51
  pytest = "^6.2.5"
52
- pydocstyle = "^6.3.0"
52
+ pydocstyle = "^6.3.0"