Ayon-Python-Api 0.1.6__tar.gz → 0.1.7__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-0.1.6/ayon_python_api.egg-info → ayon-python-api-0.1.7}/PKG-INFO +1 -1
  2. {ayon-python-api-0.1.6 → ayon-python-api-0.1.7}/ayon_api/__init__.py +13 -0
  3. {ayon-python-api-0.1.6 → ayon-python-api-0.1.7}/ayon_api/server.py +201 -14
  4. {ayon-python-api-0.1.6 → ayon-python-api-0.1.7}/ayon_api/server_api.py +15 -0
  5. {ayon-python-api-0.1.6 → ayon-python-api-0.1.7}/ayon_api/utils.py +99 -0
  6. {ayon-python-api-0.1.6 → ayon-python-api-0.1.7}/ayon_api/version.py +1 -1
  7. {ayon-python-api-0.1.6 → ayon-python-api-0.1.7/ayon_python_api.egg-info}/PKG-INFO +1 -1
  8. {ayon-python-api-0.1.6 → ayon-python-api-0.1.7}/pyproject.toml +1 -1
  9. {ayon-python-api-0.1.6 → ayon-python-api-0.1.7}/LICENSE +0 -0
  10. {ayon-python-api-0.1.6 → ayon-python-api-0.1.7}/README.md +0 -0
  11. {ayon-python-api-0.1.6 → ayon-python-api-0.1.7}/ayon_api/constants.py +0 -0
  12. {ayon-python-api-0.1.6 → ayon-python-api-0.1.7}/ayon_api/entity_hub.py +0 -0
  13. {ayon-python-api-0.1.6 → ayon-python-api-0.1.7}/ayon_api/events.py +0 -0
  14. {ayon-python-api-0.1.6 → ayon-python-api-0.1.7}/ayon_api/exceptions.py +0 -0
  15. {ayon-python-api-0.1.6 → ayon-python-api-0.1.7}/ayon_api/graphql.py +0 -0
  16. {ayon-python-api-0.1.6 → ayon-python-api-0.1.7}/ayon_api/graphql_queries.py +0 -0
  17. {ayon-python-api-0.1.6 → ayon-python-api-0.1.7}/ayon_api/operations.py +0 -0
  18. {ayon-python-api-0.1.6 → ayon-python-api-0.1.7}/ayon_api/thumbnails.py +0 -0
  19. {ayon-python-api-0.1.6 → ayon-python-api-0.1.7}/ayon_python_api.egg-info/SOURCES.txt +0 -0
  20. {ayon-python-api-0.1.6 → ayon-python-api-0.1.7}/ayon_python_api.egg-info/dependency_links.txt +0 -0
  21. {ayon-python-api-0.1.6 → ayon-python-api-0.1.7}/ayon_python_api.egg-info/requires.txt +0 -0
  22. {ayon-python-api-0.1.6 → ayon-python-api-0.1.7}/ayon_python_api.egg-info/top_level.txt +0 -0
  23. {ayon-python-api-0.1.6 → ayon-python-api-0.1.7}/setup.cfg +0 -0
  24. {ayon-python-api-0.1.6 → ayon-python-api-0.1.7}/setup.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: ayon-python-api
3
- Version: 0.1.6
3
+ Version: 0.1.7
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,6 @@
1
+ from .utils import (
2
+ TransferProgress,
3
+ )
1
4
  from .server import (
2
5
  ServerAPIBase,
3
6
  )
@@ -7,6 +10,9 @@ from .server_api import (
7
10
 
8
11
  get_server_api_connection,
9
12
 
13
+ get_base_url,
14
+ get_rest_url,
15
+
10
16
  raw_get,
11
17
  raw_post,
12
18
  raw_put,
@@ -25,6 +31,7 @@ from .server_api import (
25
31
  query_graphql,
26
32
 
27
33
  get_addons_info,
34
+ download_addon_private_file,
28
35
 
29
36
  get_dependencies_info,
30
37
  update_dependency_info,
@@ -91,12 +98,17 @@ from .server_api import (
91
98
 
92
99
 
93
100
  __all__ = (
101
+ "TransferProgress",
102
+
94
103
  "ServerAPIBase",
95
104
 
96
105
  "ServerAPI",
97
106
 
98
107
  "get_server_api_connection",
99
108
 
109
+ "get_base_url",
110
+ "get_rest_url",
111
+
100
112
  "raw_get",
101
113
  "raw_post",
102
114
  "raw_put",
@@ -115,6 +127,7 @@ __all__ = (
115
127
  "query_graphql",
116
128
 
117
129
  "get_addons_info",
130
+ "download_addon_private_file",
118
131
 
119
132
  "get_dependencies_info",
120
133
  "update_dependency_info",
@@ -1,5 +1,6 @@
1
1
  import os
2
2
  import re
3
+ import io
3
4
  import json
4
5
  import logging
5
6
  import collections
@@ -43,7 +44,8 @@ from .utils import (
43
44
  logout_from_server,
44
45
  create_entity_id,
45
46
  entity_data_json_default,
46
- failed_json_default
47
+ failed_json_default,
48
+ TransferProgress,
47
49
  )
48
50
 
49
51
  JSONDecodeError = getattr(json, "JSONDecodeError", ValueError)
@@ -200,6 +202,15 @@ class ServerAPIBase(object):
200
202
 
201
203
  self._thumbnail_cache = ThumbnailCache(True)
202
204
 
205
+ def get_base_url(self):
206
+ return self._base_url
207
+
208
+ def get_rest_url(self):
209
+ return self._rest_url
210
+
211
+ base_url = property(get_base_url)
212
+ rest_url = property(get_rest_url)
213
+
203
214
  @property
204
215
  def access_token(self):
205
216
  return self._access_token
@@ -544,22 +555,125 @@ class ServerAPIBase(object):
544
555
  def delete(self, entrypoint, **kwargs):
545
556
  return self.raw_delete(entrypoint, params=kwargs)
546
557
 
547
- def download_file(self, endpoint, filepath, chunk_size=None):
548
- if not chunk_size:
549
- # 1 MB chunk by default
550
- chunk_size = 1024 * 1024
558
+ def _download_file(self, url, filepath, chunk_size, progress):
551
559
  dst_directory = os.path.dirname(filepath)
552
560
  if not os.path.exists(dst_directory):
553
561
  os.makedirs(dst_directory)
554
562
 
563
+ kwargs = {"stream": True}
564
+ if self._session is None:
565
+ kwargs["headers"] = self.get_headers()
566
+ get_func = self._base_functions_mapping[RequestTypes.get]
567
+ else:
568
+ get_func = self._session_functions_mapping[RequestTypes.get]
569
+
555
570
  with open(filepath, "wb") as f_stream:
556
- with self.raw_get(endpoint, stream=True) as response:
571
+ with get_func(url, **kwargs) as response:
572
+ response.raise_for_status()
573
+ progress.set_content_size(response.headers["Content-length"])
557
574
  for chunk in response.iter_content(chunk_size=chunk_size):
558
575
  f_stream.write(chunk)
576
+ progress.add_downloaded_chunk(len(chunk))
577
+
578
+ def download_file(self, endpoint, filepath, chunk_size=None, progress=None):
579
+ """Download file from AYON server.
580
+
581
+ Endpoint can be full url (must start with 'base_url' of api object).
582
+
583
+ Progress object can be used to track download. Can be used when
584
+ download happens in thread and other thread want to catch changes over
585
+ time.
586
+
587
+ Args:
588
+ endpoint (str): Endpoint or URL to file that should be downloaded.
589
+ filepath (str): Path where file will be downloaded.
590
+ chunk_size (int): Size of chunks that are received in single loop.
591
+ progress (TransferProgress): Object that gives ability to track
592
+ download progress.
593
+ """
594
+
595
+ if not chunk_size:
596
+ # 1 MB chunk by default
597
+ chunk_size = 1024 * 1024
598
+
599
+ if endpoint.startswith(self._base_url):
600
+ url = endpoint
601
+ else:
602
+ endpoint = endpoint.lstrip("/").rstrip("/")
603
+ url = "{}/{}".format(self._rest_url, endpoint)
604
+
605
+ # Create dummy object so the function does not have to check
606
+ # 'progress' variable everywhere
607
+ if progress is None:
608
+ progress = TransferProgress()
609
+
610
+ progress.set_source_url(url)
611
+ progress.set_destination_url(filepath)
612
+ progress.set_started()
613
+ try:
614
+ self._download_file(url, filepath, chunk_size, progress)
615
+
616
+ except Exception as exc:
617
+ progress.set_failed(str(exc))
618
+ raise
619
+
620
+ finally:
621
+ progress.set_done()
622
+
623
+ def _upload_file(self, url, filepath, progress):
624
+ kwargs = {}
625
+ if self._session is None:
626
+ kwargs["headers"] = self.get_headers()
627
+ post_func = self._base_functions_mapping[RequestTypes.post]
628
+ else:
629
+ post_func = self._session_functions_mapping[RequestTypes.post]
559
630
 
560
- def upload_file(self, endpoint, filepath):
561
631
  with open(filepath, "rb") as stream:
562
- self.raw_post(endpoint, data=stream)
632
+ stream.seek(0, io.SEEK_END)
633
+ size = stream.tell()
634
+ stream.seek(0)
635
+ progress.set_content_size(size)
636
+ response = post_func(url, data=stream, **kwargs)
637
+ response.raise_for_status()
638
+ progress.set_downloaded_size(size)
639
+
640
+ def upload_file(self, endpoint, filepath, progress=None):
641
+ """Upload file to server.
642
+
643
+ Todos:
644
+ Uploading with more detailed progress.
645
+
646
+ Args:
647
+ endpoint (str): Endpoint or url where file will be uploaded.
648
+ filepath (str): Source filepath.
649
+ progress (TransferProgress): Object that gives ability to track
650
+ upload progress.
651
+ """
652
+
653
+ if endpoint.startswith(self._base_url):
654
+ url = endpoint
655
+ else:
656
+ endpoint = endpoint.lstrip("/").rstrip("/")
657
+ url = "{}/{}".format(self._rest_url, endpoint)
658
+
659
+ # Create dummy object so the function does not have to check
660
+ # 'progress' variable everywhere
661
+ if progress is None:
662
+ progress = TransferProgress()
663
+
664
+ progress.set_source_url(filepath)
665
+ progress.set_destination_url(url)
666
+ progress.set_started()
667
+
668
+ try:
669
+ self._upload_file(url, filepath, progress)
670
+
671
+ except Exception as exc:
672
+ progress.set_failed(str(exc))
673
+ raise
674
+
675
+ finally:
676
+ progress.set_done()
563
677
 
564
678
  def trigger_server_restart(self):
565
679
  result = self.post("system/restart")
@@ -779,6 +893,55 @@ class ServerAPIBase(object):
779
893
  raise ServerError(response.text)
780
894
  return response.data
781
895
 
896
+ def download_addon_private_file(
897
+ self,
898
+ addon_name,
899
+ addon_version,
900
+ filename,
901
+ destination_dir,
902
+ destination_filename=None,
903
+ chunk_size=None,
904
+ progress=None,
905
+ ):
906
+ """Download a file from addon private files.
907
+
908
+ This method requires to have authorized token available. Private files
909
+ are not under '/api' restpoint.
910
+
911
+ Args:
912
+ addon_name (str): Addon name.
913
+ addon_version (str): Addon version.
914
+ filename (str): Filename in private folder on server.
915
+ destination_dir (str): Where the file should be downloaded.
916
+ destination_filename (str): Name of destination filename. Source
917
+ filename is used if not passed.
918
+ chunk_size (int): Download chunk size.
919
+ progress (TransferProgress): Object that gives ability to track
920
+ download progress.
921
+
922
+ Returns:
923
+ str: Filepath to downloaded file.
924
+ """
925
+
926
+ if not destination_filename:
927
+ destination_filename = filename
928
+ dst_filepath = os.path.join(destination_dir, destination_filename)
929
+ # Filename can contain "subfolders"
930
+ dst_dirpath = os.path.dirname(dst_filepath)
931
+ if not os.path.exists(dst_dirpath):
932
+ os.makedirs(dst_dirpath)
933
+
934
+ url = "{}/addons/{}/{}/private/{}".format(
935
+ self._base_url,
936
+ addon_name,
937
+ addon_version,
938
+ filename
939
+ )
940
+ self.download_file(
941
+ url, dst_filepath, chunk_size=chunk_size, progress=progress
942
+ )
943
+ return dst_filepath
944
+
782
945
  def get_dependencies_info(self):
783
946
  result = self.get("dependencies")
784
947
  return result.data
@@ -839,29 +1002,53 @@ class ServerAPIBase(object):
839
1002
 
840
1003
  def download_dependency_package(
841
1004
  self,
842
- dst_directory,
843
1005
  package_name,
844
- platform_name=None
1006
+ dst_directory,
1007
+ filename,
1008
+ platform_name=None,
1009
+ chunk_size=None,
1010
+ progress=None,
845
1011
  ):
1012
+ """Download dependency package from server.
1013
+
1014
+ This method requires to have authorized token available. The package is
1015
+ only downloaded.
1016
+
1017
+ Args:
1018
+ package_name (str): Name of package to download.
1019
+ dst_directory (str): Where the file should be downloaded.
1020
+ filename (str): Name of destination filename.
1021
+ platform_name (str): Name of platform for which the dependency
1022
+ package is targetter. Default value is current platform.
1023
+ chunk_size (int): Download chunk size.
1024
+ progress (TransferProgress): Object that gives ability to track
1025
+ download progress.
1026
+
1027
+ Returns:
1028
+ str: Filepath to downloaded file.
1029
+ """
846
1030
  if platform_name is None:
847
1031
  platform_name = platform.system().lower()
848
1032
 
849
- package_filepath = os.path.join(dst_directory, package_name + ".zip")
1033
+ package_filepath = os.path.join(dst_directory, filename)
850
1034
  self.download_file(
851
1035
  "dependencies/{}/{}".format(package_name, platform_name),
852
- package_filepath
1036
+ package_filepath,
1037
+ chunk_size=chunk_size,
1038
+ progress=progress
853
1039
  )
854
1040
  return package_filepath
855
1041
 
856
1042
  def upload_dependency_package(
857
- self, filepath, package_name, platform_name=None
1043
+ self, filepath, package_name, platform_name=None, progress=None
858
1044
  ):
859
1045
  if platform_name is None:
860
1046
  platform_name = platform.system().lower()
861
1047
 
862
1048
  self.upload_file(
863
1049
  "dependencies/{}/{}".format(package_name, platform_name),
864
- filepath
1050
+ filepath,
1051
+ progress=progress
865
1052
  )
866
1053
 
867
1054
  def delete_dependency_package(self, package_name, platform_name=None):
@@ -85,6 +85,16 @@ def get_server_api_connection():
85
85
  return GlobalContext.get_server_api_connection()
86
86
 
87
87
 
88
+ def get_base_url():
89
+ con = get_server_api_connection()
90
+ return con.get_base_url()
91
+
92
+
93
+ def get_rest_url():
94
+ con = get_server_api_connection()
95
+ return con.get_rest_url()
96
+
97
+
88
98
  def raw_get(*args, **kwargs):
89
99
  con = get_server_api_connection()
90
100
  return con.raw_get(*args, **kwargs)
@@ -170,6 +180,11 @@ def get_addons_info(*args, **kwargs):
170
180
  return con.get_addons_info(*args, **kwargs)
171
181
 
172
182
 
183
+ def download_addon_private_file(*args, **kwargs):
184
+ con = get_server_api_connection()
185
+ return con.download_addon_private_file(*args, **kwargs)
186
+
187
+
173
188
  def get_dependencies_info(*args, **kwargs):
174
189
  con = get_server_api_connection()
175
190
  return con.get_dependencies_info(*args, **kwargs)
@@ -265,3 +265,102 @@ def validate_url(url):
265
265
  title="Couldn't connect to server",
266
266
  hints=hints + universal_hints
267
267
  )
268
+
269
+
270
+ class TransferProgress:
271
+ """Object to store progress of download/upload from/to server."""
272
+
273
+ def __init__(self):
274
+ self._started = False
275
+ self._done = False
276
+ self._downloaded = 0
277
+ self._content_size = None
278
+
279
+ self._failed = False
280
+ self._fail_reason = None
281
+
282
+ self._source_url = "N/A"
283
+ self._destination_url = "N/A"
284
+
285
+ def get_content_size(self):
286
+ return self._content_size
287
+
288
+ def set_content_size(self, content_size):
289
+ if self._content_size is not None:
290
+ raise ValueError("Content size was set more then once")
291
+ self._content_size = content_size
292
+
293
+ def get_started(self):
294
+ return self._started
295
+
296
+ def set_started(self):
297
+ if self._started:
298
+ raise ValueError("Progress already started")
299
+ self._started = True
300
+
301
+ def get_done(self):
302
+ return self._done
303
+
304
+ def set_done(self):
305
+ if self._done:
306
+ raise ValueError("Progress was already marked as done")
307
+ if not self._started:
308
+ raise ValueError("Progress didn't start yet")
309
+ self._done = True
310
+
311
+ def get_failed(self):
312
+ return self._failed
313
+
314
+ def get_fail_reason(self):
315
+ return self._fail_reason
316
+
317
+ def set_failed(self, reason):
318
+ self._fail_reason = reason
319
+ self._failed = True
320
+
321
+ def get_downloaded_size(self):
322
+ return self._downloaded
323
+
324
+ def set_downloaded_size(self, downloaded_size):
325
+ self._downloaded = downloaded_size
326
+
327
+ def add_downloaded_chunk(self, chunk_size):
328
+ self._downloaded += chunk_size
329
+
330
+ def get_source_url(self):
331
+ return self._source_url
332
+
333
+ def set_source_url(self, url):
334
+ self._source_url = url
335
+
336
+ def get_destination_url(self):
337
+ return self._destination_url
338
+
339
+ def set_destination_url(self, url):
340
+ self._destination_url = url
341
+
342
+ @property
343
+ def is_running(self):
344
+ if (
345
+ not self.started
346
+ or self.done
347
+ or self.failed
348
+ ):
349
+ return False
350
+ return True
351
+
352
+ @property
353
+ def progress(self):
354
+ if self._content_size is None:
355
+ return None
356
+ return (self._downloaded * 100.0) / float(self._content_size)
357
+
358
+ content_size = property(get_content_size, set_content_size)
359
+ started = property(get_started)
360
+ done = property(get_done)
361
+ failed = property(get_failed)
362
+ fail_reason = property(get_fail_reason)
363
+ source_url = property(get_source_url, set_source_url)
364
+ destination_url = property(get_destination_url, set_destination_url)
365
+ content_size = property(get_content_size, set_content_size)
366
+ downloaded_size = property(get_downloaded_size, set_downloaded_size)
@@ -1,2 +1,2 @@
1
1
  """Package declaring Python API for Ayon server."""
2
- __version__ = "0.1.6"
2
+ __version__ = "0.1.7"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: ayon-python-api
3
- Version: 0.1.6
3
+ Version: 0.1.7
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
  [tool.poetry]
2
2
  name = "ayon_api"
3
- version = "0.1.6"
3
+ version = "0.1.7"
4
4
  description = "Python api for AYON server"
5
5
  authors = ["ynput.io <info@ynput.io>"]
6
6
 
File without changes