ayon-python-api 1.0.7__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.
- {ayon-python-api-1.0.7 → ayon-python-api-1.0.8}/PKG-INFO +1 -1
- {ayon-python-api-1.0.7 → ayon-python-api-1.0.8}/ayon_api/__init__.py +10 -0
- {ayon-python-api-1.0.7 → ayon-python-api-1.0.8}/ayon_api/_api.py +23 -0
- {ayon-python-api-1.0.7 → ayon-python-api-1.0.8}/ayon_api/server_api.py +86 -8
- {ayon-python-api-1.0.7 → ayon-python-api-1.0.8}/ayon_api/utils.py +138 -6
- {ayon-python-api-1.0.7 → ayon-python-api-1.0.8}/ayon_api/version.py +1 -1
- {ayon-python-api-1.0.7 → ayon-python-api-1.0.8}/ayon_python_api.egg-info/PKG-INFO +1 -1
- {ayon-python-api-1.0.7 → ayon-python-api-1.0.8}/pyproject.toml +2 -2
- {ayon-python-api-1.0.7 → ayon-python-api-1.0.8}/LICENSE +0 -0
- {ayon-python-api-1.0.7 → ayon-python-api-1.0.8}/README.md +0 -0
- {ayon-python-api-1.0.7 → ayon-python-api-1.0.8}/ayon_api/constants.py +0 -0
- {ayon-python-api-1.0.7 → ayon-python-api-1.0.8}/ayon_api/entity_hub.py +0 -0
- {ayon-python-api-1.0.7 → ayon-python-api-1.0.8}/ayon_api/events.py +0 -0
- {ayon-python-api-1.0.7 → ayon-python-api-1.0.8}/ayon_api/exceptions.py +0 -0
- {ayon-python-api-1.0.7 → ayon-python-api-1.0.8}/ayon_api/graphql.py +0 -0
- {ayon-python-api-1.0.7 → ayon-python-api-1.0.8}/ayon_api/graphql_queries.py +0 -0
- {ayon-python-api-1.0.7 → ayon-python-api-1.0.8}/ayon_api/operations.py +0 -0
- {ayon-python-api-1.0.7 → ayon-python-api-1.0.8}/ayon_python_api.egg-info/SOURCES.txt +0 -0
- {ayon-python-api-1.0.7 → ayon-python-api-1.0.8}/ayon_python_api.egg-info/dependency_links.txt +0 -0
- {ayon-python-api-1.0.7 → ayon-python-api-1.0.8}/ayon_python_api.egg-info/requires.txt +0 -0
- {ayon-python-api-1.0.7 → ayon-python-api-1.0.8}/ayon_python_api.egg-info/top_level.txt +0 -0
- {ayon-python-api-1.0.7 → ayon-python-api-1.0.8}/setup.cfg +0 -0
- {ayon-python-api-1.0.7 → ayon-python-api-1.0.8}/setup.py +0 -0
|
@@ -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,
|
|
@@ -67,6 +71,7 @@ from ._api import (
|
|
|
67
71
|
download_file,
|
|
68
72
|
upload_file_from_stream,
|
|
69
73
|
upload_file,
|
|
74
|
+
upload_reviewable,
|
|
70
75
|
trigger_server_restart,
|
|
71
76
|
query_graphql,
|
|
72
77
|
get_graphql_schema,
|
|
@@ -229,6 +234,10 @@ __all__ = (
|
|
|
229
234
|
"TransferProgress",
|
|
230
235
|
"slugify_string",
|
|
231
236
|
"create_dependency_package_basename",
|
|
237
|
+
"get_user_by_token",
|
|
238
|
+
"is_token_valid",
|
|
239
|
+
"validate_url",
|
|
240
|
+
"login_to_server",
|
|
232
241
|
|
|
233
242
|
"RequestTypes",
|
|
234
243
|
"ServerAPI",
|
|
@@ -290,6 +299,7 @@ __all__ = (
|
|
|
290
299
|
"download_file",
|
|
291
300
|
"upload_file_from_stream",
|
|
292
301
|
"upload_file",
|
|
302
|
+
"upload_reviewable",
|
|
293
303
|
"trigger_server_restart",
|
|
294
304
|
"query_graphql",
|
|
295
305
|
"get_graphql_schema",
|
|
@@ -938,6 +938,29 @@ def upload_file(*args, **kwargs):
|
|
|
938
938
|
return con.upload_file(*args, **kwargs)
|
|
939
939
|
|
|
940
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
|
+
|
|
941
964
|
def trigger_server_restart():
|
|
942
965
|
"""Trigger server restart.
|
|
943
966
|
|
|
@@ -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(""))
|
|
@@ -1652,7 +1653,13 @@ class ServerAPI(object):
|
|
|
1652
1653
|
response = self.post("enroll", **kwargs)
|
|
1653
1654
|
if response.status_code == 204:
|
|
1654
1655
|
return None
|
|
1655
|
-
|
|
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:
|
|
1656
1663
|
self.log.error(response.text)
|
|
1657
1664
|
return None
|
|
1658
1665
|
|
|
@@ -1931,6 +1938,77 @@ class ServerAPI(object):
|
|
|
1931
1938
|
endpoint, stream, progress, request_type, **kwargs
|
|
1932
1939
|
)
|
|
1933
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
|
+
|
|
1934
2012
|
def trigger_server_restart(self):
|
|
1935
2013
|
"""Trigger server restart.
|
|
1936
2014
|
|
|
@@ -8134,11 +8212,11 @@ class ServerAPI(object):
|
|
|
8134
8212
|
return op_results
|
|
8135
8213
|
|
|
8136
8214
|
def _convert_entity_data(self, entity):
|
|
8137
|
-
if not entity:
|
|
8215
|
+
if not entity or "data" not in entity:
|
|
8138
8216
|
return
|
|
8139
|
-
|
|
8140
|
-
|
|
8141
|
-
|
|
8142
|
-
|
|
8143
|
-
|
|
8144
|
-
|
|
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
|
|
@@ -348,10 +349,8 @@ def logout_from_server(url, token, timeout=None):
|
|
|
348
349
|
)
|
|
349
350
|
|
|
350
351
|
|
|
351
|
-
def
|
|
352
|
-
"""
|
|
353
|
-
|
|
354
|
-
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.
|
|
355
354
|
|
|
356
355
|
Args:
|
|
357
356
|
url (str): Server url.
|
|
@@ -360,7 +359,7 @@ def is_token_valid(url, token, timeout=None):
|
|
|
360
359
|
'get_default_timeout' is used if not specified.
|
|
361
360
|
|
|
362
361
|
Returns:
|
|
363
|
-
|
|
362
|
+
Optional[Dict[str, Any]]: User information if url and token are valid.
|
|
364
363
|
|
|
365
364
|
"""
|
|
366
365
|
if timeout is None:
|
|
@@ -381,7 +380,27 @@ def is_token_valid(url, token, timeout=None):
|
|
|
381
380
|
timeout=timeout,
|
|
382
381
|
)
|
|
383
382
|
if response.status_code == 200:
|
|
384
|
-
return
|
|
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
|
|
385
404
|
return False
|
|
386
405
|
|
|
387
406
|
|
|
@@ -704,3 +723,116 @@ def create_dependency_package_basename(platform_name=None):
|
|
|
704
723
|
now_date = datetime.datetime.now()
|
|
705
724
|
time_stamp = now_date.strftime("%y%m%d%H%M")
|
|
706
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.
|
|
2
|
+
__version__ = "1.0.8"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "ayon-python-api"
|
|
3
|
-
version = "1.0.
|
|
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.
|
|
32
|
+
version = "1.0.8"
|
|
33
33
|
description = "AYON Python API"
|
|
34
34
|
authors = [
|
|
35
35
|
"ynput.io <info@ynput.io>"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{ayon-python-api-1.0.7 → ayon-python-api-1.0.8}/ayon_python_api.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|