Ayon-Python-Api 0.3.5__tar.gz → 0.3.6__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-0.3.5 → ayon-python-api-0.3.6}/PKG-INFO +1 -1
  2. {ayon-python-api-0.3.5 → ayon-python-api-0.3.6}/ayon_api/__init__.py +10 -0
  3. {ayon-python-api-0.3.5 → ayon-python-api-0.3.6}/ayon_api/_api.py +20 -0
  4. {ayon-python-api-0.3.5 → ayon-python-api-0.3.6}/ayon_api/constants.py +3 -0
  5. {ayon-python-api-0.3.5 → ayon-python-api-0.3.6}/ayon_api/graphql_queries.py +5 -1
  6. {ayon-python-api-0.3.5 → ayon-python-api-0.3.6}/ayon_api/server_api.py +172 -29
  7. {ayon-python-api-0.3.5 → ayon-python-api-0.3.6}/ayon_api/version.py +1 -1
  8. {ayon-python-api-0.3.5 → ayon-python-api-0.3.6}/ayon_python_api.egg-info/PKG-INFO +1 -1
  9. {ayon-python-api-0.3.5 → ayon-python-api-0.3.6}/pyproject.toml +1 -1
  10. {ayon-python-api-0.3.5 → ayon-python-api-0.3.6}/LICENSE +0 -0
  11. {ayon-python-api-0.3.5 → ayon-python-api-0.3.6}/README.md +0 -0
  12. {ayon-python-api-0.3.5 → ayon-python-api-0.3.6}/ayon_api/entity_hub.py +0 -0
  13. {ayon-python-api-0.3.5 → ayon-python-api-0.3.6}/ayon_api/events.py +0 -0
  14. {ayon-python-api-0.3.5 → ayon-python-api-0.3.6}/ayon_api/exceptions.py +0 -0
  15. {ayon-python-api-0.3.5 → ayon-python-api-0.3.6}/ayon_api/graphql.py +0 -0
  16. {ayon-python-api-0.3.5 → ayon-python-api-0.3.6}/ayon_api/operations.py +0 -0
  17. {ayon-python-api-0.3.5 → ayon-python-api-0.3.6}/ayon_api/utils.py +0 -0
  18. {ayon-python-api-0.3.5 → ayon-python-api-0.3.6}/ayon_python_api.egg-info/SOURCES.txt +0 -0
  19. {ayon-python-api-0.3.5 → ayon-python-api-0.3.6}/ayon_python_api.egg-info/dependency_links.txt +0 -0
  20. {ayon-python-api-0.3.5 → ayon-python-api-0.3.6}/ayon_python_api.egg-info/requires.txt +0 -0
  21. {ayon-python-api-0.3.5 → ayon-python-api-0.3.6}/ayon_python_api.egg-info/top_level.txt +0 -0
  22. {ayon-python-api-0.3.5 → ayon-python-api-0.3.6}/setup.cfg +0 -0
  23. {ayon-python-api-0.3.5 → ayon-python-api-0.3.6}/setup.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: ayon-python-api
3
- Version: 0.3.5
3
+ Version: 0.3.6
4
4
  Summary: AYON Python API
5
5
  Home-page: https://github.com/ynput/ayon-python-api
6
6
  Author: ynput.io
@@ -48,6 +48,11 @@ from ._api import (
48
48
  patch,
49
49
  delete,
50
50
 
51
+ get_timeout,
52
+ set_timeout,
53
+ get_max_retries,
54
+ set_max_retries,
55
+
51
56
  get_event,
52
57
  get_events,
53
58
  dispatch_event,
@@ -245,6 +250,11 @@ __all__ = (
245
250
  "patch",
246
251
  "delete",
247
252
 
253
+ "get_timeout",
254
+ "set_timeout",
255
+ "get_max_retries",
256
+ "set_max_retries",
257
+
248
258
  "get_event",
249
259
  "get_events",
250
260
  "dispatch_event",
@@ -474,6 +474,26 @@ def delete(*args, **kwargs):
474
474
  return con.delete(*args, **kwargs)
475
475
 
476
476
 
477
+ def get_timeout(*args, **kwargs):
478
+ con = get_server_api_connection()
479
+ return con.get_timeout(*args, **kwargs)
480
+
481
+
482
+ def set_timeout(*args, **kwargs):
483
+ con = get_server_api_connection()
484
+ return con.set_timeout(*args, **kwargs)
485
+
486
+
487
+ def get_max_retries(*args, **kwargs):
488
+ con = get_server_api_connection()
489
+ return con.get_max_retries(*args, **kwargs)
490
+
491
+
492
+ def set_max_retries(*args, **kwargs):
493
+ con = get_server_api_connection()
494
+ return con.set_max_retries(*args, **kwargs)
495
+
496
+
477
497
  def get_event(*args, **kwargs):
478
498
  con = get_server_api_connection()
479
499
  return con.get_event(*args, **kwargs)
@@ -1,6 +1,9 @@
1
1
  # Environments where server url and api key are stored for global connection
2
2
  SERVER_URL_ENV_KEY = "AYON_SERVER_URL"
3
3
  SERVER_API_ENV_KEY = "AYON_API_KEY"
4
+ SERVER_TIMEOUT_ENV_KEY = "AYON_SERVER_TIMEOUT"
5
+ SERVER_RETRIES_ENV_KEY = "AYON_SERVER_RETRIES"
6
+
4
7
  # Backwards compatibility
5
8
  SERVER_TOKEN_ENV_KEY = SERVER_API_ENV_KEY
6
9
 
@@ -247,9 +247,11 @@ def products_graphql_query(fields):
247
247
  query = GraphQlQuery("ProductsQuery")
248
248
 
249
249
  project_name_var = query.add_variable("projectName", "String!")
250
- folder_ids_var = query.add_variable("folderIds", "[String!]")
251
250
  product_ids_var = query.add_variable("productIds", "[String!]")
252
251
  product_names_var = query.add_variable("productNames", "[String!]")
252
+ folder_ids_var = query.add_variable("folderIds", "[String!]")
253
+ product_types_var = query.add_variable("productTypes", "[String!]")
254
+ statuses_var = query.add_variable("statuses", "[String!]")
253
255
 
254
256
  project_field = query.add_field("project")
255
257
  project_field.set_filter("name", project_name_var)
@@ -258,6 +260,8 @@ def products_graphql_query(fields):
258
260
  products_field.set_filter("ids", product_ids_var)
259
261
  products_field.set_filter("names", product_names_var)
260
262
  products_field.set_filter("folderIds", folder_ids_var)
263
+ products_field.set_filter("productTypes", product_types_var)
264
+ products_field.set_filter("statuses", statuses_var)
261
265
 
262
266
  nested_fields = fields_to_dict(set(fields))
263
267
  add_links_fields(products_field, nested_fields)
@@ -2,6 +2,7 @@ import os
2
2
  import re
3
3
  import io
4
4
  import json
5
+ import time
5
6
  import logging
6
7
  import collections
7
8
  import platform
@@ -26,6 +27,8 @@ except ImportError:
26
27
  from json import JSONDecodeError as RequestsJSONDecodeError
27
28
 
28
29
  from .constants import (
30
+ SERVER_TIMEOUT_ENV_KEY,
31
+ SERVER_RETRIES_ENV_KEY,
29
32
  DEFAULT_PRODUCT_TYPE_FIELDS,
30
33
  DEFAULT_PROJECT_FIELDS,
31
34
  DEFAULT_FOLDER_FIELDS,
@@ -127,6 +130,8 @@ class RestApiResponse(object):
127
130
 
128
131
  @property
129
132
  def text(self):
133
+ if self._response is None:
134
+ return self.detail
130
135
  return self._response.text
131
136
 
132
137
  @property
@@ -135,6 +140,8 @@ class RestApiResponse(object):
135
140
 
136
141
  @property
137
142
  def headers(self):
143
+ if self._response is None:
144
+ return {}
138
145
  return self._response.headers
139
146
 
140
147
  @property
@@ -148,6 +155,8 @@ class RestApiResponse(object):
148
155
 
149
156
  @property
150
157
  def content(self):
158
+ if self._response is None:
159
+ return b""
151
160
  return self._response.content
152
161
 
153
162
  @property
@@ -339,7 +348,11 @@ class ServerAPI(object):
339
348
  variable value 'AYON_CERT_FILE' by default.
340
349
  create_session (Optional[bool]): Create session for connection if
341
350
  token is available. Default is True.
351
+ timeout (Optional[float]): Timeout for requests.
352
+ max_retries (Optional[int]): Number of retries for requests.
342
353
  """
354
+ _default_timeout = 10.0
355
+ _default_max_retries = 3
343
356
 
344
357
  def __init__(
345
358
  self,
@@ -352,6 +365,8 @@ class ServerAPI(object):
352
365
  ssl_verify=None,
353
366
  cert=None,
354
367
  create_session=True,
368
+ timeout=None,
369
+ max_retries=None,
355
370
  ):
356
371
  if not base_url:
357
372
  raise ValueError("Invalid server URL {}".format(str(base_url)))
@@ -370,6 +385,9 @@ class ServerAPI(object):
370
385
  )
371
386
  self._sender = sender
372
387
 
388
+ self._timeout = timeout
389
+ self._max_retries = max_retries
390
+
373
391
  if ssl_verify is None:
374
392
  # Custom AYON env variable for CA file or 'True'
375
393
  # - that should cover most default behaviors in 'requests'
@@ -474,6 +492,87 @@ class ServerAPI(object):
474
492
  ssl_verify = property(get_ssl_verify, set_ssl_verify)
475
493
  cert = property(get_cert, set_cert)
476
494
 
495
+ @classmethod
496
+ def get_default_timeout(cls):
497
+ """Default value for requests timeout.
498
+
499
+ First looks for environment variable SERVER_TIMEOUT_ENV_KEY which
500
+ can affect timeout value. If not available then use class
501
+ attribute '_default_timeout'.
502
+
503
+ Returns:
504
+ float: Timeout value in seconds.
505
+ """
506
+
507
+ try:
508
+ return float(os.environ.get(SERVER_TIMEOUT_ENV_KEY))
509
+ except (ValueError, TypeError):
510
+ pass
511
+
512
+ return cls._default_timeout
513
+
514
+ @classmethod
515
+ def get_default_max_retries(cls):
516
+ """Default value for requests max retries.
517
+
518
+ First looks for environment variable SERVER_RETRIES_ENV_KEY, which
519
+ can affect max retries value. If not available then use class
520
+ attribute '_default_max_retries'.
521
+
522
+ Returns:
523
+ int: Max retries value.
524
+ """
525
+
526
+ try:
527
+ return int(os.environ.get(SERVER_RETRIES_ENV_KEY))
528
+ except (ValueError, TypeError):
529
+ pass
530
+
531
+ return cls._default_max_retries
532
+
533
+ def get_timeout(self):
534
+ """Current value for requests timeout.
535
+
536
+ Returns:
537
+ float: Timeout value in seconds.
538
+ """
539
+
540
+ return self._timeout
541
+
542
+ def set_timeout(self, timeout):
543
+ """Change timeout value for requests.
544
+
545
+ Args:
546
+ timeout (Union[float, None]): Timeout value in seconds.
547
+ """
548
+
549
+ if timeout is None:
550
+ timeout = self.get_default_timeout()
551
+ self._timeout = float(timeout)
552
+
553
+ def get_max_retries(self):
554
+ """Current value for requests max retries.
555
+
556
+ Returns:
557
+ int: Max retries value.
558
+ """
559
+
560
+ return self._max_retries
561
+
562
+ def set_max_retries(self, max_retries):
563
+ """Change max retries value for requests.
564
+
565
+ Args:
566
+ max_retries (Union[int, None]): Max retries value.
567
+ """
568
+
569
+ if max_retries is None:
570
+ max_retries = self.get_default_max_retries()
571
+ self._max_retries = int(max_retries)
572
+
573
+ timeout = property(get_timeout, set_timeout)
574
+ max_retries = property(get_max_retries, set_max_retries)
575
+
477
576
  @property
478
577
  def access_token(self):
479
578
  """Access token used for authorization to server.
@@ -1004,6 +1103,10 @@ class ServerAPI(object):
1004
1103
  logout_from_server(self._base_url, self._access_token)
1005
1104
 
1006
1105
  def _do_rest_request(self, function, url, **kwargs):
1106
+ kwargs.setdefault("timeout", self.timeout)
1107
+ max_retries = kwargs.get("max_retries", self.max_retries)
1108
+ if max_retries < 1:
1109
+ max_retries = 1
1007
1110
  if self._session is None:
1008
1111
  # Validate token if was not yet validated
1009
1112
  # - ignore validation if we're in middle of
@@ -1023,38 +1126,54 @@ class ServerAPI(object):
1023
1126
  elif isinstance(function, RequestType):
1024
1127
  function = self._session_functions_mapping[function]
1025
1128
 
1026
- try:
1027
- response = function(url, **kwargs)
1129
+ response = None
1130
+ new_response = None
1131
+ for _ in range(max_retries):
1132
+ try:
1133
+ response = function(url, **kwargs)
1134
+ break
1135
+
1136
+ except ConnectionRefusedError:
1137
+ # Server may be restarting
1138
+ new_response = RestApiResponse(
1139
+ None,
1140
+ {"detail": "Unable to connect the server. Connection refused"}
1141
+ )
1142
+ except requests.exceptions.Timeout:
1143
+ # Connection timed out
1144
+ new_response = RestApiResponse(
1145
+ None,
1146
+ {"detail": "Connection timed out."}
1147
+ )
1148
+ except requests.exceptions.ConnectionError:
1149
+ # Other connection error (ssl, etc) - does not make sense to
1150
+ # try call server again
1151
+ new_response = RestApiResponse(
1152
+ None,
1153
+ {"detail": "Unable to connect the server. Connection error"}
1154
+ )
1155
+ break
1028
1156
 
1029
- except ConnectionRefusedError:
1030
- new_response = RestApiResponse(
1031
- None,
1032
- {"detail": "Unable to connect the server. Connection refused"}
1033
- )
1034
- except requests.exceptions.ConnectionError:
1035
- new_response = RestApiResponse(
1036
- None,
1037
- {"detail": "Unable to connect the server. Connection error"}
1038
- )
1039
- else:
1040
- content_type = response.headers.get("Content-Type")
1041
- if content_type == "application/json":
1042
- try:
1043
- new_response = RestApiResponse(response)
1044
- except JSONDecodeError:
1045
- new_response = RestApiResponse(
1046
- None,
1047
- {
1048
- "detail": "The response is not a JSON: {}".format(
1049
- response.text)
1050
- }
1051
- )
1157
+ time.sleep(0.1)
1052
1158
 
1053
- elif content_type in ("image/jpeg", "image/png"):
1054
- new_response = RestApiResponse(response)
1159
+ if new_response is not None:
1160
+ return new_response
1055
1161
 
1056
- else:
1162
+ content_type = response.headers.get("Content-Type")
1163
+ if content_type == "application/json":
1164
+ try:
1057
1165
  new_response = RestApiResponse(response)
1166
+ except JSONDecodeError:
1167
+ new_response = RestApiResponse(
1168
+ None,
1169
+ {
1170
+ "detail": "The response is not a JSON: {}".format(
1171
+ response.text)
1172
+ }
1173
+ )
1174
+
1175
+ else:
1176
+ new_response = RestApiResponse(response)
1058
1177
 
1059
1178
  self.log.debug("Response {}".format(str(new_response)))
1060
1179
  return new_response
@@ -3810,6 +3929,8 @@ class ServerAPI(object):
3810
3929
  product_ids=None,
3811
3930
  product_names=None,
3812
3931
  folder_ids=None,
3932
+ product_types=None,
3933
+ statuses=None,
3813
3934
  names_by_folder_ids=None,
3814
3935
  active=True,
3815
3936
  fields=None,
@@ -3828,6 +3949,10 @@ class ServerAPI(object):
3828
3949
  filtering.
3829
3950
  folder_ids (Optional[Iterable[str]]): Ids of task parents.
3830
3951
  Use 'None' if folder is direct child of project.
3952
+ product_types (Optional[Iterable[str]]): Product types used for
3953
+ filtering.
3954
+ statuses (Optional[Iterable[str]]): Product statuses used for
3955
+ filtering.
3831
3956
  names_by_folder_ids (Optional[dict[str, Iterable[str]]]): Product
3832
3957
  name filtering by folder id.
3833
3958
  active (Optional[bool]): Filter active/inactive products.
@@ -3862,6 +3987,18 @@ class ServerAPI(object):
3862
3987
  if not filter_folder_ids:
3863
3988
  return
3864
3989
 
3990
+ filter_product_types = None
3991
+ if product_types is not None:
3992
+ filter_product_types = set(product_types)
3993
+ if not filter_product_types:
3994
+ return
3995
+
3996
+ filter_statuses = None
3997
+ if statuses is not None:
3998
+ filter_statuses = set(statuses)
3999
+ if not filter_statuses:
4000
+ return
4001
+
3865
4002
  # This will disable 'folder_ids' and 'product_names' filters
3866
4003
  # - maybe could be enhanced in future?
3867
4004
  if names_by_folder_ids is not None:
@@ -3881,7 +4018,7 @@ class ServerAPI(object):
3881
4018
  fields = set(fields) | {"id"}
3882
4019
  if "attrib" in fields:
3883
4020
  fields.remove("attrib")
3884
- fields |= self.get_attributes_fields_for_type("folder")
4021
+ fields |= self.get_attributes_fields_for_type("product")
3885
4022
  else:
3886
4023
  fields = self.get_default_fields_for_type("product")
3887
4024
 
@@ -3908,6 +4045,12 @@ class ServerAPI(object):
3908
4045
  if filter_folder_ids:
3909
4046
  filters["folderIds"] = list(filter_folder_ids)
3910
4047
 
4048
+ if filter_product_types:
4049
+ filters["productTypes"] = list(filter_product_types)
4050
+
4051
+ if filter_statuses:
4052
+ filters["statuses"] = list(filter_statuses)
4053
+
3911
4054
  if product_ids:
3912
4055
  filters["productIds"] = list(product_ids)
3913
4056
 
@@ -1,2 +1,2 @@
1
1
  """Package declaring Python API for Ayon server."""
2
- __version__ = "0.3.5"
2
+ __version__ = "0.3.6"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: ayon-python-api
3
- Version: 0.3.5
3
+ Version: 0.3.6
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 = "0.3.5"
3
+ version = "0.3.6"
4
4
  description = "AYON Python API"
5
5
  license = {file = "LICENSE"}
6
6
  readme = {file = "README.md", content-type = "text/markdown"}
File without changes