shotgun-api3 3.9.0__py2.py3-none-any.whl → 3.9.2__py2.py3-none-any.whl

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.
shotgun_api3/shotgun.py CHANGED
@@ -29,6 +29,8 @@ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29
29
  OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30
30
  """
31
31
 
32
+ from __future__ import annotations # Requried for 3.7
33
+
32
34
  import base64
33
35
  import copy
34
36
  import datetime
@@ -50,6 +52,17 @@ import urllib.parse
50
52
  import urllib.request
51
53
  import uuid # used for attachment upload
52
54
  import xml.etree.ElementTree
55
+ from typing import (
56
+ Any,
57
+ BinaryIO,
58
+ Dict,
59
+ Iterable,
60
+ List,
61
+ Optional,
62
+ Tuple,
63
+ TypeVar,
64
+ Union,
65
+ )
53
66
 
54
67
  # Import Error and ResponseError (even though they're unused in this file) since they need
55
68
  # to be exposed as part of the API.
@@ -81,7 +94,35 @@ SHOTGUN_API_DISABLE_ENTITY_OPTIMIZATION = False
81
94
 
82
95
  # ----------------------------------------------------------------------------
83
96
  # Version
84
- __version__ = "3.9.0"
97
+ __version__ = "3.9.2"
98
+
99
+
100
+ # ----------------------------------------------------------------------------
101
+ # Types
102
+
103
+
104
+ T = TypeVar("T")
105
+
106
+ if sys.version_info < (3, 9):
107
+ OrderItem = Dict
108
+ GroupingItem = Dict
109
+ BaseEntity = Dict
110
+ else:
111
+ from typing import TypedDict
112
+
113
+ class OrderItem(TypedDict):
114
+ field_name: str
115
+ direction: str
116
+
117
+ class GroupingItem(TypedDict):
118
+ field: str
119
+ type: str
120
+ direction: str
121
+
122
+ class BaseEntity(TypedDict, total=False):
123
+ id: int
124
+ type: str
125
+
85
126
 
86
127
  # ----------------------------------------------------------------------------
87
128
  # Errors
@@ -168,7 +209,7 @@ class ServerCapabilities(object):
168
209
  the future. Therefore, usage of this class is discouraged.
169
210
  """
170
211
 
171
- def __init__(self, host, meta):
212
+ def __init__(self, host: str, meta: Dict[str, Any]) -> None:
172
213
  """
173
214
  ServerCapabilities.__init__
174
215
 
@@ -181,7 +222,6 @@ class ServerCapabilities(object):
181
222
  :ivar bool is_dev: ``True`` if server is running a development version of the Shotgun
182
223
  codebase.
183
224
  """
184
- self._ensure_python_version_supported()
185
225
  # Server host name
186
226
  self.host = host
187
227
  self.server_info = meta
@@ -208,14 +248,7 @@ class ServerCapabilities(object):
208
248
  self.version = tuple(self.version[:3])
209
249
  self._ensure_json_supported()
210
250
 
211
- def _ensure_python_version_supported(self):
212
- """
213
- Checks the if current Python version is supported.
214
- """
215
- if sys.version_info < (3, 7):
216
- raise ShotgunError("This module requires Python version 3.7 or higher.")
217
-
218
- def _ensure_support(self, feature, raise_hell=True):
251
+ def _ensure_support(self, feature: Dict[str, Any], raise_hell: bool = True) -> bool:
219
252
  """
220
253
  Checks the server version supports a given feature, raises an exception if it does not.
221
254
 
@@ -243,13 +276,13 @@ class ServerCapabilities(object):
243
276
  else:
244
277
  return True
245
278
 
246
- def _ensure_json_supported(self):
279
+ def _ensure_json_supported(self) -> None:
247
280
  """
248
281
  Ensures server has support for JSON API endpoint added in v2.4.0.
249
282
  """
250
283
  self._ensure_support({"version": (2, 4, 0), "label": "JSON API"})
251
284
 
252
- def ensure_include_archived_projects(self):
285
+ def ensure_include_archived_projects(self) -> None:
253
286
  """
254
287
  Ensures server has support for archived Projects feature added in v5.3.14.
255
288
  """
@@ -257,7 +290,7 @@ class ServerCapabilities(object):
257
290
  {"version": (5, 3, 14), "label": "include_archived_projects parameter"}
258
291
  )
259
292
 
260
- def ensure_per_project_customization(self):
293
+ def ensure_per_project_customization(self) -> bool:
261
294
  """
262
295
  Ensures server has support for per-project customization feature added in v5.4.4.
263
296
  """
@@ -265,7 +298,7 @@ class ServerCapabilities(object):
265
298
  {"version": (5, 4, 4), "label": "project parameter"}, True
266
299
  )
267
300
 
268
- def ensure_support_for_additional_filter_presets(self):
301
+ def ensure_support_for_additional_filter_presets(self) -> bool:
269
302
  """
270
303
  Ensures server has support for additional filter presets feature added in v7.0.0.
271
304
  """
@@ -273,7 +306,7 @@ class ServerCapabilities(object):
273
306
  {"version": (7, 0, 0), "label": "additional_filter_presets parameter"}, True
274
307
  )
275
308
 
276
- def ensure_user_following_support(self):
309
+ def ensure_user_following_support(self) -> bool:
277
310
  """
278
311
  Ensures server has support for listing items a user is following, added in v7.0.12.
279
312
  """
@@ -281,7 +314,7 @@ class ServerCapabilities(object):
281
314
  {"version": (7, 0, 12), "label": "user_following parameter"}, True
282
315
  )
283
316
 
284
- def ensure_paging_info_without_counts_support(self):
317
+ def ensure_paging_info_without_counts_support(self) -> bool:
285
318
  """
286
319
  Ensures server has support for optimized pagination, added in v7.4.0.
287
320
  """
@@ -289,7 +322,7 @@ class ServerCapabilities(object):
289
322
  {"version": (7, 4, 0), "label": "optimized pagination"}, False
290
323
  )
291
324
 
292
- def ensure_return_image_urls_support(self):
325
+ def ensure_return_image_urls_support(self) -> bool:
293
326
  """
294
327
  Ensures server has support for returning thumbnail URLs without additional round-trips, added in v3.3.0.
295
328
  """
@@ -297,7 +330,7 @@ class ServerCapabilities(object):
297
330
  {"version": (3, 3, 0), "label": "return thumbnail URLs"}, False
298
331
  )
299
332
 
300
- def __str__(self):
333
+ def __str__(self) -> str:
301
334
  return "ServerCapabilities: host %s, version %s, is_dev %s" % (
302
335
  self.host,
303
336
  self.version,
@@ -355,7 +388,7 @@ class _Config(object):
355
388
  Container for the client configuration.
356
389
  """
357
390
 
358
- def __init__(self, sg):
391
+ def __init__(self, sg: "Shotgun"):
359
392
  """
360
393
  :param sg: Shotgun connection.
361
394
  """
@@ -376,41 +409,41 @@ class _Config(object):
376
409
  # If the optional timeout parameter is given, blocking operations
377
410
  # (like connection attempts) will timeout after that many seconds
378
411
  # (if it is not given, the global default timeout setting is used)
379
- self.timeout_secs = None
412
+ self.timeout_secs: Optional[float] = None
380
413
  self.api_ver = "api3"
381
414
  self.convert_datetimes_to_utc = True
382
- self._records_per_page = None
383
- self.api_key = None
384
- self.script_name = None
385
- self.user_login = None
386
- self.user_password = None
387
- self.auth_token = None
388
- self.sudo_as_login = None
415
+ self._records_per_page: Optional[int] = None
416
+ self.api_key: Optional[str] = None
417
+ self.script_name: Optional[str] = None
418
+ self.user_login: Optional[str] = None
419
+ self.user_password: Optional[str] = None
420
+ self.auth_token: Optional[str] = None
421
+ self.sudo_as_login: Optional[str] = None
389
422
  # Authentication parameters to be folded into final auth_params dict
390
- self.extra_auth_params = None
423
+ self.extra_auth_params: Optional[Dict[str, Any]] = None
391
424
  # uuid as a string
392
- self.session_uuid = None
393
- self.scheme = None
394
- self.server = None
395
- self.api_path = None
425
+ self.session_uuid: Optional[str] = None
426
+ self.scheme: Optional[str] = None
427
+ self.server: Optional[str] = None
428
+ self.api_path: Optional[str] = None
396
429
  # The raw_http_proxy reflects the exact string passed in
397
430
  # to the Shotgun constructor. This can be useful if you
398
431
  # need to construct a Shotgun API instance based on
399
432
  # another Shotgun API instance.
400
- self.raw_http_proxy = None
433
+ self.raw_http_proxy: Optional[str] = None
401
434
  # if a proxy server is being used, the proxy_handler
402
435
  # below will contain a urllib2.ProxyHandler instance
403
436
  # which can be used whenever a request needs to be made.
404
- self.proxy_handler = None
405
- self.proxy_server = None
437
+ self.proxy_handler: Optional["urllib.request.ProxyHandler"] = None
438
+ self.proxy_server: Optional[str] = None
406
439
  self.proxy_port = 8080
407
- self.proxy_user = None
408
- self.proxy_pass = None
409
- self.session_token = None
410
- self.authorization = None
440
+ self.proxy_user: Optional[str] = None
441
+ self.proxy_pass: Optional[str] = None
442
+ self.session_token: Optional[str] = None
443
+ self.authorization: Optional[str] = None
411
444
  self.localized = False
412
445
 
413
- def set_server_params(self, base_url):
446
+ def set_server_params(self, base_url: str) -> None:
414
447
  """
415
448
  Set the different server related fields based on the passed in URL.
416
449
 
@@ -432,7 +465,7 @@ class _Config(object):
432
465
  )
433
466
 
434
467
  @property
435
- def records_per_page(self):
468
+ def records_per_page(self) -> int:
436
469
  """
437
470
  The records per page value from the server.
438
471
  """
@@ -465,19 +498,19 @@ class Shotgun(object):
465
498
 
466
499
  def __init__(
467
500
  self,
468
- base_url,
469
- script_name=None,
470
- api_key=None,
471
- convert_datetimes_to_utc=True,
472
- http_proxy=None,
473
- connect=True,
474
- ca_certs=None,
475
- login=None,
476
- password=None,
477
- sudo_as_login=None,
478
- session_token=None,
479
- auth_token=None,
480
- ):
501
+ base_url: str,
502
+ script_name: Optional[str] = None,
503
+ api_key: Optional[str] = None,
504
+ convert_datetimes_to_utc: bool = True,
505
+ http_proxy: Optional[str] = None,
506
+ connect: bool = True,
507
+ ca_certs: Optional[str] = None,
508
+ login: Optional[str] = None,
509
+ password: Optional[str] = None,
510
+ sudo_as_login: Optional[str] = None,
511
+ session_token: Optional[str] = None,
512
+ auth_token: Optional[str] = None,
513
+ ) -> None:
481
514
  """
482
515
  Initializes a new instance of the Shotgun client.
483
516
 
@@ -589,7 +622,7 @@ class Shotgun(object):
589
622
  "must provide login/password, session_token or script_name/api_key"
590
623
  )
591
624
 
592
- self.config = _Config(self)
625
+ self.config: _Config = _Config(self)
593
626
  self.config.api_key = api_key
594
627
  self.config.script_name = script_name
595
628
  self.config.user_login = login
@@ -625,7 +658,7 @@ class Shotgun(object):
625
658
  ):
626
659
  SHOTGUN_API_DISABLE_ENTITY_OPTIMIZATION = True
627
660
 
628
- self._connection = None
661
+ self._connection: Optional[Http] = None
629
662
 
630
663
  self.__ca_certs = self._get_certs_file(ca_certs)
631
664
 
@@ -690,7 +723,7 @@ class Shotgun(object):
690
723
  # this relies on self.client_caps being set first
691
724
  self.reset_user_agent()
692
725
 
693
- self._server_caps = None
726
+ self._server_caps: Optional[ServerCapabilities] = None
694
727
  # test to ensure the the server supports the json API
695
728
  # call to server will only be made once and will raise error
696
729
  if connect:
@@ -704,7 +737,7 @@ class Shotgun(object):
704
737
  self.config.user_password = None
705
738
  self.config.auth_token = None
706
739
 
707
- def _split_url(self, base_url):
740
+ def _split_url(self, base_url: str) -> Tuple[Optional[str], Optional[str]]:
708
741
  """
709
742
  Extract the hostname:port and username/password/token from base_url
710
743
  sent when connect to the API.
@@ -736,7 +769,7 @@ class Shotgun(object):
736
769
  # API Functions
737
770
 
738
771
  @property
739
- def server_info(self):
772
+ def server_info(self) -> Dict[str, Any]:
740
773
  """
741
774
  Property containing server information.
742
775
 
@@ -754,7 +787,7 @@ class Shotgun(object):
754
787
  return self.server_caps.server_info
755
788
 
756
789
  @property
757
- def server_caps(self):
790
+ def server_caps(self) -> ServerCapabilities:
758
791
  """
759
792
  Property containing :class:`ServerCapabilities` object.
760
793
 
@@ -769,7 +802,7 @@ class Shotgun(object):
769
802
  self._server_caps = ServerCapabilities(self.config.server, self.info())
770
803
  return self._server_caps
771
804
 
772
- def connect(self):
805
+ def connect(self) -> None:
773
806
  """
774
807
  Connect client to the server if it is not already connected.
775
808
 
@@ -780,7 +813,7 @@ class Shotgun(object):
780
813
  self.info()
781
814
  return
782
815
 
783
- def close(self):
816
+ def close(self) -> None:
784
817
  """
785
818
  Close the current connection to the server.
786
819
 
@@ -789,7 +822,7 @@ class Shotgun(object):
789
822
  self._close_connection()
790
823
  return
791
824
 
792
- def info(self):
825
+ def info(self) -> Dict[str, Any]:
793
826
  """
794
827
  Get API-related metadata from the Shotgun server.
795
828
 
@@ -822,15 +855,15 @@ class Shotgun(object):
822
855
 
823
856
  def find_one(
824
857
  self,
825
- entity_type,
826
- filters,
827
- fields=None,
828
- order=None,
829
- filter_operator=None,
830
- retired_only=False,
831
- include_archived_projects=True,
832
- additional_filter_presets=None,
833
- ):
858
+ entity_type: str,
859
+ filters: Union[List, Tuple, Dict[str, Any]],
860
+ fields: Optional[List[str]] = None,
861
+ order: Optional[List[OrderItem]] = None,
862
+ filter_operator: Optional[str] = None,
863
+ retired_only: bool = False,
864
+ include_archived_projects: bool = True,
865
+ additional_filter_presets: Optional[List[Dict[str, Any]]] = None,
866
+ ) -> Optional[BaseEntity]:
834
867
  """
835
868
  Shortcut for :meth:`~shotgun_api3.Shotgun.find` with ``limit=1`` so it returns a single
836
869
  result.
@@ -845,7 +878,7 @@ class Shotgun(object):
845
878
 
846
879
  :param list fields: Optional list of fields to include in each entity record returned.
847
880
  Defaults to ``["id"]``.
848
- :param int order: Optional list of fields to order the results by. List has the format::
881
+ :param list order: Optional list of fields to order the results by. List has the format::
849
882
 
850
883
  [
851
884
  {'field_name':'foo', 'direction':'asc'},
@@ -862,7 +895,7 @@ class Shotgun(object):
862
895
  same query.
863
896
  :param bool include_archived_projects: Optional boolean flag to include entities whose projects
864
897
  have been archived. Defaults to ``True``.
865
- :param additional_filter_presets: Optional list of presets to further filter the result
898
+ :param list additional_filter_presets: Optional list of presets to further filter the result
866
899
  set, list has the form::
867
900
 
868
901
  [{
@@ -902,17 +935,17 @@ class Shotgun(object):
902
935
 
903
936
  def find(
904
937
  self,
905
- entity_type,
906
- filters,
907
- fields=None,
908
- order=None,
909
- filter_operator=None,
910
- limit=0,
911
- retired_only=False,
912
- page=0,
913
- include_archived_projects=True,
914
- additional_filter_presets=None,
915
- ):
938
+ entity_type: str,
939
+ filters: Union[List, Tuple, Dict[str, Any]],
940
+ fields: Optional[List[str]] = None,
941
+ order: Optional[List[OrderItem]] = None,
942
+ filter_operator: Optional[str] = None,
943
+ limit: int = 0,
944
+ retired_only: bool = False,
945
+ page: int = 0,
946
+ include_archived_projects: bool = True,
947
+ additional_filter_presets: Optional[List[Dict[str, Any]]] = None,
948
+ ) -> List[BaseEntity]:
916
949
  """
917
950
  Find entities matching the given filters.
918
951
 
@@ -990,7 +1023,7 @@ class Shotgun(object):
990
1023
  same query.
991
1024
  :param bool include_archived_projects: Optional boolean flag to include entities whose projects
992
1025
  have been archived. Defaults to ``True``.
993
- :param additional_filter_presets: Optional list of presets to further filter the result
1026
+ :param list additional_filter_presets: Optional list of presets to further filter the result
994
1027
  set, list has the form::
995
1028
 
996
1029
  [{
@@ -1101,15 +1134,15 @@ class Shotgun(object):
1101
1134
 
1102
1135
  def _construct_read_parameters(
1103
1136
  self,
1104
- entity_type,
1105
- fields,
1106
- filters,
1107
- retired_only,
1108
- order,
1109
- include_archived_projects,
1110
- additional_filter_presets,
1111
- ):
1112
- params = {}
1137
+ entity_type: str,
1138
+ fields: Optional[List[str]],
1139
+ filters: Dict[str, Any],
1140
+ retired_only: bool,
1141
+ order: Optional[List[Dict[str, Any]]],
1142
+ include_archived_projects: bool,
1143
+ additional_filter_presets: Optional[List[Dict[str, Any]]],
1144
+ ) -> Dict[str, Any]:
1145
+ params: Dict[str, Any] = {}
1113
1146
  params["type"] = entity_type
1114
1147
  params["return_fields"] = fields or ["id"]
1115
1148
  params["filters"] = filters
@@ -1139,7 +1172,9 @@ class Shotgun(object):
1139
1172
  params["sorts"] = sort_list
1140
1173
  return params
1141
1174
 
1142
- def _add_project_param(self, params, project_entity):
1175
+ def _add_project_param(
1176
+ self, params: Dict[str, Any], project_entity
1177
+ ) -> Dict[str, Any]:
1143
1178
 
1144
1179
  if project_entity and self.server_caps.ensure_per_project_customization():
1145
1180
  params["project"] = project_entity
@@ -1147,14 +1182,18 @@ class Shotgun(object):
1147
1182
  return params
1148
1183
 
1149
1184
  def _translate_update_params(
1150
- self, entity_type, entity_id, data, multi_entity_update_modes
1151
- ):
1185
+ self,
1186
+ entity_type: str,
1187
+ entity_id: int,
1188
+ data: Dict,
1189
+ multi_entity_update_modes: Optional[Dict],
1190
+ ) -> Dict[str, Any]:
1152
1191
  global SHOTGUN_API_DISABLE_ENTITY_OPTIMIZATION
1153
1192
 
1154
1193
  def optimize_field(field_dict):
1155
1194
  if SHOTGUN_API_DISABLE_ENTITY_OPTIMIZATION:
1156
1195
  return field_dict
1157
- return {k: _get_type_and_id_from_value(v) for k, v in field_dict.items()}
1196
+ return {k: _optimize_filter_field(v) for k, v in field_dict.items()}
1158
1197
 
1159
1198
  full_fields = self._dict_to_list(
1160
1199
  data,
@@ -1170,13 +1209,13 @@ class Shotgun(object):
1170
1209
 
1171
1210
  def summarize(
1172
1211
  self,
1173
- entity_type,
1174
- filters,
1175
- summary_fields,
1176
- filter_operator=None,
1177
- grouping=None,
1178
- include_archived_projects=True,
1179
- ):
1212
+ entity_type: str,
1213
+ filters: Union[List, Dict[str, Any]],
1214
+ summary_fields: List[Dict[str, str]],
1215
+ filter_operator: Optional[str] = None,
1216
+ grouping: Optional[List[GroupingItem]] = None,
1217
+ include_archived_projects: bool = True,
1218
+ ) -> Dict[str, Any]:
1180
1219
  """
1181
1220
  Summarize field data returned by a query.
1182
1221
 
@@ -1376,7 +1415,12 @@ class Shotgun(object):
1376
1415
  records = self._call_rpc("summarize", params)
1377
1416
  return records
1378
1417
 
1379
- def create(self, entity_type, data, return_fields=None):
1418
+ def create(
1419
+ self,
1420
+ entity_type: str,
1421
+ data: Dict[str, Any],
1422
+ return_fields: Optional[List[str]] = None,
1423
+ ) -> Dict[str, Any]:
1380
1424
  """
1381
1425
  Create a new entity of the specified ``entity_type``.
1382
1426
 
@@ -1459,7 +1503,13 @@ class Shotgun(object):
1459
1503
 
1460
1504
  return result
1461
1505
 
1462
- def update(self, entity_type, entity_id, data, multi_entity_update_modes=None):
1506
+ def update(
1507
+ self,
1508
+ entity_type: str,
1509
+ entity_id: int,
1510
+ data: Dict[str, Any],
1511
+ multi_entity_update_modes: Optional[Dict[str, Any]] = None,
1512
+ ) -> BaseEntity:
1463
1513
  """
1464
1514
  Update the specified entity with the supplied data.
1465
1515
 
@@ -1538,7 +1588,7 @@ class Shotgun(object):
1538
1588
 
1539
1589
  return result
1540
1590
 
1541
- def delete(self, entity_type, entity_id):
1591
+ def delete(self, entity_type: str, entity_id: int) -> bool:
1542
1592
  """
1543
1593
  Retire the specified entity.
1544
1594
 
@@ -1562,7 +1612,7 @@ class Shotgun(object):
1562
1612
 
1563
1613
  return self._call_rpc("delete", params)
1564
1614
 
1565
- def revive(self, entity_type, entity_id):
1615
+ def revive(self, entity_type: str, entity_id: int) -> bool:
1566
1616
  """
1567
1617
  Revive an entity that has previously been deleted.
1568
1618
 
@@ -1580,7 +1630,7 @@ class Shotgun(object):
1580
1630
 
1581
1631
  return self._call_rpc("revive", params)
1582
1632
 
1583
- def batch(self, requests):
1633
+ def batch(self, requests: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
1584
1634
  """
1585
1635
  Make a batch request of several :meth:`~shotgun_api3.Shotgun.create`,
1586
1636
  :meth:`~shotgun_api3.Shotgun.update`, and :meth:`~shotgun_api3.Shotgun.delete` calls.
@@ -1695,7 +1745,13 @@ class Shotgun(object):
1695
1745
  records = self._call_rpc("batch", calls)
1696
1746
  return self._parse_records(records)
1697
1747
 
1698
- def work_schedule_read(self, start_date, end_date, project=None, user=None):
1748
+ def work_schedule_read(
1749
+ self,
1750
+ start_date: str,
1751
+ end_date: str,
1752
+ project: Optional[Dict[str, Any]] = None,
1753
+ user: Optional[Dict[str, Any]] = None,
1754
+ ) -> Dict[str, Any]:
1699
1755
  """
1700
1756
  Return the work day rules for a given date range.
1701
1757
 
@@ -1766,13 +1822,13 @@ class Shotgun(object):
1766
1822
 
1767
1823
  def work_schedule_update(
1768
1824
  self,
1769
- date,
1770
- working,
1771
- description=None,
1772
- project=None,
1773
- user=None,
1774
- recalculate_field=None,
1775
- ):
1825
+ date: str,
1826
+ working: bool,
1827
+ description: Optional[str] = None,
1828
+ project: Optional[Dict[str, Any]] = None,
1829
+ user: Optional[Dict[str, Any]] = None,
1830
+ recalculate_field: Optional[str] = None,
1831
+ ) -> Dict[str, Any]:
1776
1832
  """
1777
1833
  Update the work schedule for a given date.
1778
1834
 
@@ -1826,7 +1882,30 @@ class Shotgun(object):
1826
1882
 
1827
1883
  return self._call_rpc("work_schedule_update", params)
1828
1884
 
1829
- def follow(self, user, entity):
1885
+ def export_page(self, page_id, format, layout_name=None):
1886
+ """
1887
+ Export the specified page to the given format.
1888
+ This method allows you to export a page to CSV.
1889
+ Respective layout or page should be marked as API Exportable in the Flow Production Tracking UI.
1890
+ For more information, see documentation_.
1891
+ .. _documentation: https://help.autodesk.com/view/SGSUB/ENU/?guid=SG_Tutorials_tu_export_csv_html#enable-api-export-for-a-page
1892
+ If ``layout_name`` is not passed in, the default layout name will be used.
1893
+ >>> sg.export_page(12345, "csv", layout_name="My Layout")
1894
+ "ID,Name,Status\\n1,Shot 001,ip\\n2, Shot 002,rev\\n"
1895
+ >>> sg.export_page(12345, "csv")
1896
+ "ID,Name,Status\\n1,Shot 001,ip\\n2,Shot 002,rev\\n"
1897
+ :param int page_id: The ID of the page to export.
1898
+ :param str format: The format to export the page to. Supported format is ``"csv"``.
1899
+ :param str layout_name: Optional layout name. This should be the name of the layout seen in the Flow Production Tracking UI.
1900
+ :returns: string containing data of the given page.
1901
+ :rtype: string
1902
+ """
1903
+
1904
+ params = dict(format=format, page_id=page_id, layout_name=layout_name)
1905
+
1906
+ return self._call_rpc("export_page", params)
1907
+
1908
+ def follow(self, user: Dict[str, Any], entity: Dict[str, Any]) -> Dict[str, Any]:
1830
1909
  """
1831
1910
  Add the entity to the user's followed entities.
1832
1911
 
@@ -1854,7 +1933,7 @@ class Shotgun(object):
1854
1933
 
1855
1934
  return self._call_rpc("follow", params)
1856
1935
 
1857
- def unfollow(self, user, entity):
1936
+ def unfollow(self, user: Dict[str, Any], entity: Dict[str, Any]) -> Dict[str, Any]:
1858
1937
  """
1859
1938
  Remove entity from the user's followed entities.
1860
1939
 
@@ -1881,7 +1960,7 @@ class Shotgun(object):
1881
1960
 
1882
1961
  return self._call_rpc("unfollow", params)
1883
1962
 
1884
- def followers(self, entity):
1963
+ def followers(self, entity: Dict[str, Any]) -> List[Dict[str, Any]]:
1885
1964
  """
1886
1965
  Return all followers for an entity.
1887
1966
 
@@ -1909,7 +1988,12 @@ class Shotgun(object):
1909
1988
 
1910
1989
  return self._call_rpc("followers", params)
1911
1990
 
1912
- def following(self, user, project=None, entity_type=None):
1991
+ def following(
1992
+ self,
1993
+ user: Dict[str, Any],
1994
+ project: Optional[Dict[str, Any]] = None,
1995
+ entity_type: Optional[str] = None,
1996
+ ) -> List[BaseEntity]:
1913
1997
  """
1914
1998
  Return all entity instances a user is following.
1915
1999
 
@@ -1940,7 +2024,9 @@ class Shotgun(object):
1940
2024
 
1941
2025
  return self._call_rpc("following", params)
1942
2026
 
1943
- def schema_entity_read(self, project_entity=None):
2027
+ def schema_entity_read(
2028
+ self, project_entity: Optional[BaseEntity] = None
2029
+ ) -> Dict[str, Dict[str, Any]]:
1944
2030
  """
1945
2031
  Return all active entity types, their display names, and their visibility.
1946
2032
 
@@ -1975,7 +2061,7 @@ class Shotgun(object):
1975
2061
  The returned display names for this method will be localized when the ``localize`` Shotgun config property is set to ``True``. See :ref:`localization` for more information.
1976
2062
  """
1977
2063
 
1978
- params = {}
2064
+ params: Dict[str, Any] = {}
1979
2065
 
1980
2066
  params = self._add_project_param(params, project_entity)
1981
2067
 
@@ -1984,7 +2070,9 @@ class Shotgun(object):
1984
2070
  else:
1985
2071
  return self._call_rpc("schema_entity_read", None)
1986
2072
 
1987
- def schema_read(self, project_entity=None):
2073
+ def schema_read(
2074
+ self, project_entity: Optional[BaseEntity] = None
2075
+ ) -> Dict[str, Dict[str, Any]]:
1988
2076
  """
1989
2077
  Get the schema for all fields on all entities.
1990
2078
 
@@ -2047,7 +2135,7 @@ class Shotgun(object):
2047
2135
  The returned display names for this method will be localized when the ``localize`` Shotgun config property is set to ``True``. See :ref:`localization` for more information.
2048
2136
  """
2049
2137
 
2050
- params = {}
2138
+ params: Dict[str, Any] = {}
2051
2139
 
2052
2140
  params = self._add_project_param(params, project_entity)
2053
2141
 
@@ -2056,7 +2144,12 @@ class Shotgun(object):
2056
2144
  else:
2057
2145
  return self._call_rpc("schema_read", None)
2058
2146
 
2059
- def schema_field_read(self, entity_type, field_name=None, project_entity=None):
2147
+ def schema_field_read(
2148
+ self,
2149
+ entity_type: str,
2150
+ field_name: Optional[str] = None,
2151
+ project_entity: Optional[BaseEntity] = None,
2152
+ ) -> Dict[str, Dict[str, Any]]:
2060
2153
  """
2061
2154
  Get schema for all fields on the specified entity type or just the field name specified
2062
2155
  if provided.
@@ -2121,8 +2214,12 @@ class Shotgun(object):
2121
2214
  return self._call_rpc("schema_field_read", params)
2122
2215
 
2123
2216
  def schema_field_create(
2124
- self, entity_type, data_type, display_name, properties=None
2125
- ):
2217
+ self,
2218
+ entity_type: str,
2219
+ data_type: str,
2220
+ display_name: str,
2221
+ properties: Optional[Dict[str, Any]] = None,
2222
+ ) -> str:
2126
2223
  """
2127
2224
  Create a field for the specified entity type.
2128
2225
 
@@ -2160,8 +2257,12 @@ class Shotgun(object):
2160
2257
  return self._call_rpc("schema_field_create", params)
2161
2258
 
2162
2259
  def schema_field_update(
2163
- self, entity_type, field_name, properties, project_entity=None
2164
- ):
2260
+ self,
2261
+ entity_type: str,
2262
+ field_name: str,
2263
+ properties: Dict[str, Any],
2264
+ project_entity: Optional[BaseEntity] = None,
2265
+ ) -> bool:
2165
2266
  """
2166
2267
  Update the properties for the specified field on an entity.
2167
2268
 
@@ -2175,9 +2276,9 @@ class Shotgun(object):
2175
2276
  >>> sg.schema_field_update("Asset", "sg_test_number", properties)
2176
2277
  True
2177
2278
 
2178
- :param entity_type: Entity type of field to update.
2179
- :param field_name: Internal Shotgun name of the field to update.
2180
- :param properties: Dictionary with key/value pairs where the key is the property to be
2279
+ :param str entity_type: Entity type of field to update.
2280
+ :param str field_name: Internal Shotgun name of the field to update.
2281
+ :param dict properties: Dictionary with key/value pairs where the key is the property to be
2181
2282
  updated and the value is the new value.
2182
2283
  :param dict project_entity: Optional Project entity specifying which project to modify the
2183
2284
  ``visible`` property for. If ``visible`` is present in ``properties`` and
@@ -2202,7 +2303,7 @@ class Shotgun(object):
2202
2303
  params = self._add_project_param(params, project_entity)
2203
2304
  return self._call_rpc("schema_field_update", params)
2204
2305
 
2205
- def schema_field_delete(self, entity_type, field_name):
2306
+ def schema_field_delete(self, entity_type: str, field_name: str) -> bool:
2206
2307
  """
2207
2308
  Delete the specified field from the entity type.
2208
2309
 
@@ -2219,7 +2320,7 @@ class Shotgun(object):
2219
2320
 
2220
2321
  return self._call_rpc("schema_field_delete", params)
2221
2322
 
2222
- def add_user_agent(self, agent):
2323
+ def add_user_agent(self, agent: str) -> None:
2223
2324
  """
2224
2325
  Add agent to the user-agent header.
2225
2326
 
@@ -2231,7 +2332,7 @@ class Shotgun(object):
2231
2332
  """
2232
2333
  self._user_agents.append(agent)
2233
2334
 
2234
- def reset_user_agent(self):
2335
+ def reset_user_agent(self) -> None:
2235
2336
  """
2236
2337
  Reset user agent to the default value.
2237
2338
 
@@ -2251,7 +2352,7 @@ class Shotgun(object):
2251
2352
  "ssl %s" % (self.client_caps.ssl_version),
2252
2353
  ]
2253
2354
 
2254
- def set_session_uuid(self, session_uuid):
2355
+ def set_session_uuid(self, session_uuid: str) -> None:
2255
2356
  """
2256
2357
  Set the browser session_uuid in the current Shotgun API instance.
2257
2358
 
@@ -2269,12 +2370,12 @@ class Shotgun(object):
2269
2370
 
2270
2371
  def share_thumbnail(
2271
2372
  self,
2272
- entities,
2273
- thumbnail_path=None,
2274
- source_entity=None,
2275
- filmstrip_thumbnail=False,
2276
- **kwargs,
2277
- ):
2373
+ entities: List[Dict[str, Any]],
2374
+ thumbnail_path: Optional[str] = None,
2375
+ source_entity: Optional[BaseEntity] = None,
2376
+ filmstrip_thumbnail: bool = False,
2377
+ **kwargs: Any,
2378
+ ) -> int:
2278
2379
  """
2279
2380
  Associate a thumbnail with more than one Shotgun entity.
2280
2381
 
@@ -2413,7 +2514,9 @@ class Shotgun(object):
2413
2514
 
2414
2515
  return attachment_id
2415
2516
 
2416
- def upload_thumbnail(self, entity_type, entity_id, path, **kwargs):
2517
+ def upload_thumbnail(
2518
+ self, entity_type: str, entity_id: int, path: str, **kwargs: Any
2519
+ ) -> int:
2417
2520
  """
2418
2521
  Upload a file from a local path and assign it as the thumbnail for the specified entity.
2419
2522
 
@@ -2438,12 +2541,15 @@ class Shotgun(object):
2438
2541
  :param int entity_id: Id of the entity to set the thumbnail for.
2439
2542
  :param str path: Full path to the thumbnail file on disk.
2440
2543
  :returns: Id of the new attachment
2544
+ :rtype: int
2441
2545
  """
2442
2546
  return self.upload(
2443
2547
  entity_type, entity_id, path, field_name="thumb_image", **kwargs
2444
2548
  )
2445
2549
 
2446
- def upload_filmstrip_thumbnail(self, entity_type, entity_id, path, **kwargs):
2550
+ def upload_filmstrip_thumbnail(
2551
+ self, entity_type: str, entity_id: int, path: str, **kwargs: Any
2552
+ ) -> int:
2447
2553
  """
2448
2554
  Upload filmstrip thumbnail to specified entity.
2449
2555
 
@@ -2494,13 +2600,13 @@ class Shotgun(object):
2494
2600
 
2495
2601
  def upload(
2496
2602
  self,
2497
- entity_type,
2498
- entity_id,
2499
- path,
2500
- field_name=None,
2501
- display_name=None,
2502
- tag_list=None,
2503
- ):
2603
+ entity_type: str,
2604
+ entity_id: int,
2605
+ path: str,
2606
+ field_name: Optional[str] = None,
2607
+ display_name: Optional[str] = None,
2608
+ tag_list: Optional[str] = None,
2609
+ ) -> int:
2504
2610
  """
2505
2611
  Upload a file to the specified entity.
2506
2612
 
@@ -2583,14 +2689,14 @@ class Shotgun(object):
2583
2689
 
2584
2690
  def _upload_to_storage(
2585
2691
  self,
2586
- entity_type,
2587
- entity_id,
2588
- path,
2589
- field_name,
2590
- display_name,
2591
- tag_list,
2592
- is_thumbnail,
2593
- ):
2692
+ entity_type: str,
2693
+ entity_id: int,
2694
+ path: str,
2695
+ field_name: Optional[str],
2696
+ display_name: Optional[str],
2697
+ tag_list: Optional[str],
2698
+ is_thumbnail: bool,
2699
+ ) -> int:
2594
2700
  """
2595
2701
  Internal function to upload a file to the Cloud storage and link it to the specified entity.
2596
2702
 
@@ -2673,14 +2779,14 @@ class Shotgun(object):
2673
2779
 
2674
2780
  def _upload_to_sg(
2675
2781
  self,
2676
- entity_type,
2677
- entity_id,
2678
- path,
2679
- field_name,
2680
- display_name,
2681
- tag_list,
2682
- is_thumbnail,
2683
- ):
2782
+ entity_type: str,
2783
+ entity_id: int,
2784
+ path: str,
2785
+ field_name: Optional[str],
2786
+ display_name: Optional[str],
2787
+ tag_list: Optional[str],
2788
+ is_thumbnail: bool,
2789
+ ) -> int:
2684
2790
  """
2685
2791
  Internal function to upload a file to Shotgun and link it to the specified entity.
2686
2792
 
@@ -2752,7 +2858,9 @@ class Shotgun(object):
2752
2858
  attachment_id = int(result.split(":", 2)[1].split("\n", 1)[0])
2753
2859
  return attachment_id
2754
2860
 
2755
- def _get_attachment_upload_info(self, is_thumbnail, filename, is_multipart_upload):
2861
+ def _get_attachment_upload_info(
2862
+ self, is_thumbnail: bool, filename: str, is_multipart_upload: bool
2863
+ ) -> Dict[str, Any]:
2756
2864
  """
2757
2865
  Internal function to get the information needed to upload a file to Cloud storage.
2758
2866
 
@@ -2799,7 +2907,12 @@ class Shotgun(object):
2799
2907
  "upload_info": upload_info,
2800
2908
  }
2801
2909
 
2802
- def download_attachment(self, attachment=False, file_path=None, attachment_id=None):
2910
+ def download_attachment(
2911
+ self,
2912
+ attachment: Union[Dict[str, Any], bool] = False,
2913
+ file_path: Optional[str] = None,
2914
+ attachment_id: Optional[int] = None,
2915
+ ) -> Union[str, bytes, None]:
2803
2916
  """
2804
2917
  Download the file associated with a Shotgun Attachment.
2805
2918
 
@@ -2915,7 +3028,7 @@ class Shotgun(object):
2915
3028
  else:
2916
3029
  return attachment
2917
3030
 
2918
- def get_auth_cookie_handler(self):
3031
+ def get_auth_cookie_handler(self) -> urllib.request.HTTPCookieProcessor:
2919
3032
  """
2920
3033
  Return an urllib cookie handler containing a cookie for FPTR
2921
3034
  authentication.
@@ -2947,7 +3060,9 @@ class Shotgun(object):
2947
3060
  cj.set_cookie(c)
2948
3061
  return urllib.request.HTTPCookieProcessor(cj)
2949
3062
 
2950
- def get_attachment_download_url(self, attachment):
3063
+ def get_attachment_download_url(
3064
+ self, attachment: Optional[Union[int, dict[str, Any]]]
3065
+ ) -> str:
2951
3066
  """
2952
3067
  Return the URL for downloading provided Attachment.
2953
3068
 
@@ -3005,7 +3120,9 @@ class Shotgun(object):
3005
3120
  )
3006
3121
  return url
3007
3122
 
3008
- def authenticate_human_user(self, user_login, user_password, auth_token=None):
3123
+ def authenticate_human_user(
3124
+ self, user_login: str, user_password: str, auth_token: Optional[str] = None
3125
+ ) -> Union[Dict[str, Any], None]:
3009
3126
  """
3010
3127
  Authenticate Shotgun HumanUser.
3011
3128
 
@@ -3064,7 +3181,9 @@ class Shotgun(object):
3064
3181
  self.config.auth_token = original_auth_token
3065
3182
  raise
3066
3183
 
3067
- def update_project_last_accessed(self, project, user=None):
3184
+ def update_project_last_accessed(
3185
+ self, project: Dict[str, Any], user: Optional[Dict[str, Any]] = None
3186
+ ) -> None:
3068
3187
  """
3069
3188
  Update a Project's ``last_accessed_by_current_user`` field to the current timestamp.
3070
3189
 
@@ -3110,7 +3229,9 @@ class Shotgun(object):
3110
3229
  record = self._call_rpc("update_project_last_accessed_by_current_user", params)
3111
3230
  self._parse_records(record)[0]
3112
3231
 
3113
- def note_thread_read(self, note_id, entity_fields=None):
3232
+ def note_thread_read(
3233
+ self, note_id: int, entity_fields: Optional[Dict[str, Any]] = None
3234
+ ) -> List[Dict[str, Any]]:
3114
3235
  """
3115
3236
  Return the full conversation for a given note, including Replies and Attachments.
3116
3237
 
@@ -3185,7 +3306,13 @@ class Shotgun(object):
3185
3306
  result = self._parse_records(record)
3186
3307
  return result
3187
3308
 
3188
- def text_search(self, text, entity_types, project_ids=None, limit=None):
3309
+ def text_search(
3310
+ self,
3311
+ text: str,
3312
+ entity_types: Dict[str, Any],
3313
+ project_ids: Optional[List] = None,
3314
+ limit: Optional[int] = None,
3315
+ ) -> Dict[str, Any]:
3189
3316
  """
3190
3317
  Search across the specified entity types for the given text.
3191
3318
 
@@ -3279,13 +3406,13 @@ class Shotgun(object):
3279
3406
 
3280
3407
  def activity_stream_read(
3281
3408
  self,
3282
- entity_type,
3283
- entity_id,
3284
- entity_fields=None,
3285
- min_id=None,
3286
- max_id=None,
3287
- limit=None,
3288
- ):
3409
+ entity_type: str,
3410
+ entity_id: int,
3411
+ entity_fields: Optional[Dict[str, Any]] = None,
3412
+ min_id: Optional[int] = None,
3413
+ max_id: Optional[int] = None,
3414
+ limit: Optional[int] = None,
3415
+ ) -> Dict[str, Any]:
3289
3416
  """
3290
3417
  Retrieve activity stream data from Shotgun.
3291
3418
 
@@ -3375,7 +3502,7 @@ class Shotgun(object):
3375
3502
  result = self._parse_records(record)[0]
3376
3503
  return result
3377
3504
 
3378
- def nav_expand(self, path, seed_entity_field=None, entity_fields=None):
3505
+ def nav_expand(self, path: str, seed_entity_field=None, entity_fields=None):
3379
3506
  """
3380
3507
  Expand the navigation hierarchy for the supplied path.
3381
3508
 
@@ -3395,7 +3522,9 @@ class Shotgun(object):
3395
3522
  },
3396
3523
  )
3397
3524
 
3398
- def nav_search_string(self, root_path, search_string, seed_entity_field=None):
3525
+ def nav_search_string(
3526
+ self, root_path: str, search_string: str, seed_entity_field=None
3527
+ ):
3399
3528
  """
3400
3529
  Search function adapted to work with the navigation hierarchy.
3401
3530
 
@@ -3414,7 +3543,12 @@ class Shotgun(object):
3414
3543
  },
3415
3544
  )
3416
3545
 
3417
- def nav_search_entity(self, root_path, entity, seed_entity_field=None):
3546
+ def nav_search_entity(
3547
+ self,
3548
+ root_path: str,
3549
+ entity: Dict[str, Any],
3550
+ seed_entity_field: Optional[Dict[str, Any]] = None,
3551
+ ):
3418
3552
  """
3419
3553
  Search function adapted to work with the navigation hierarchy.
3420
3554
 
@@ -3434,7 +3568,7 @@ class Shotgun(object):
3434
3568
  },
3435
3569
  )
3436
3570
 
3437
- def get_session_token(self):
3571
+ def get_session_token(self) -> str:
3438
3572
  """
3439
3573
  Get the session token associated with the current session.
3440
3574
 
@@ -3458,7 +3592,7 @@ class Shotgun(object):
3458
3592
 
3459
3593
  return session_token
3460
3594
 
3461
- def preferences_read(self, prefs=None):
3595
+ def preferences_read(self, prefs: Optional[List] = None) -> Dict[str, Any]:
3462
3596
  """
3463
3597
  Get a subset of the site preferences.
3464
3598
 
@@ -3481,7 +3615,7 @@ class Shotgun(object):
3481
3615
 
3482
3616
  return self._call_rpc("preferences_read", {"prefs": prefs})
3483
3617
 
3484
- def user_subscriptions_read(self):
3618
+ def user_subscriptions_read(self) -> List:
3485
3619
  """
3486
3620
  Get the list of user subscriptions.
3487
3621
 
@@ -3493,8 +3627,9 @@ class Shotgun(object):
3493
3627
 
3494
3628
  return self._call_rpc("user_subscriptions_read", None)
3495
3629
 
3496
- def user_subscriptions_create(self, users):
3497
- # type: (list[dict[str, Union[str, list[str], None]) -> bool
3630
+ def user_subscriptions_create(
3631
+ self, users: List[Dict[str, Union[str, List[str], None]]]
3632
+ ) -> bool:
3498
3633
  """
3499
3634
  Assign subscriptions to users.
3500
3635
 
@@ -3515,7 +3650,7 @@ class Shotgun(object):
3515
3650
 
3516
3651
  return response.get("status") == "success"
3517
3652
 
3518
- def _build_opener(self, handler):
3653
+ def _build_opener(self, handler) -> urllib.request.OpenerDirector:
3519
3654
  """
3520
3655
  Build urllib2 opener with appropriate proxy handler.
3521
3656
  """
@@ -3616,7 +3751,13 @@ class Shotgun(object):
3616
3751
  # ========================================================================
3617
3752
  # RPC Functions
3618
3753
 
3619
- def _call_rpc(self, method, params, include_auth_params=True, first=False):
3754
+ def _call_rpc(
3755
+ self,
3756
+ method: str,
3757
+ params: Any,
3758
+ include_auth_params: bool = True,
3759
+ first: bool = False,
3760
+ ) -> Any:
3620
3761
  """
3621
3762
  Call the specified method on the Shotgun Server sending the supplied payload.
3622
3763
  """
@@ -3680,7 +3821,7 @@ class Shotgun(object):
3680
3821
  return results[0]
3681
3822
  return results
3682
3823
 
3683
- def _auth_params(self):
3824
+ def _auth_params(self) -> Dict[str, Any]:
3684
3825
  """
3685
3826
  Return a dictionary of the authentication parameters being used.
3686
3827
  """
@@ -3735,7 +3876,7 @@ class Shotgun(object):
3735
3876
 
3736
3877
  return auth_params
3737
3878
 
3738
- def _sanitize_auth_params(self, params):
3879
+ def _sanitize_auth_params(self, params: Dict[str, Any]) -> Dict[str, Any]:
3739
3880
  """
3740
3881
  Given an authentication parameter dictionary, sanitize any sensitive
3741
3882
  information and return the sanitized dict copy.
@@ -3746,7 +3887,9 @@ class Shotgun(object):
3746
3887
  sanitized_params[k] = "********"
3747
3888
  return sanitized_params
3748
3889
 
3749
- def _build_payload(self, method, params, include_auth_params=True):
3890
+ def _build_payload(
3891
+ self, method: str, params, include_auth_params: bool = True
3892
+ ) -> Dict[str, Any]:
3750
3893
  """
3751
3894
  Build the payload to be send to the rpc endpoint.
3752
3895
  """
@@ -3764,7 +3907,7 @@ class Shotgun(object):
3764
3907
 
3765
3908
  return {"method_name": method, "params": call_params}
3766
3909
 
3767
- def _encode_payload(self, payload):
3910
+ def _encode_payload(self, payload) -> bytes:
3768
3911
  """
3769
3912
  Encode the payload to a string to be passed to the rpc endpoint.
3770
3913
 
@@ -3775,7 +3918,9 @@ class Shotgun(object):
3775
3918
 
3776
3919
  return json.dumps(payload, ensure_ascii=False).encode("utf-8")
3777
3920
 
3778
- def _make_call(self, verb, path, body, headers):
3921
+ def _make_call(
3922
+ self, verb: str, path: str, body, headers: Optional[Dict[str, Any]]
3923
+ ) -> Tuple[Tuple[int, str], Dict[str, Any], str]:
3779
3924
  """
3780
3925
  Make an HTTP call to the server.
3781
3926
 
@@ -3825,7 +3970,9 @@ class Shotgun(object):
3825
3970
  )
3826
3971
  time.sleep(rpc_attempt_interval)
3827
3972
 
3828
- def _http_request(self, verb, path, body, headers):
3973
+ def _http_request(
3974
+ self, verb: str, path: str, body, headers: Dict[str, Any]
3975
+ ) -> Tuple[Tuple[int, str], Dict[str, Any], str]:
3829
3976
  """
3830
3977
  Make the actual HTTP request.
3831
3978
  """
@@ -3849,7 +3996,9 @@ class Shotgun(object):
3849
3996
 
3850
3997
  return (http_status, resp_headers, resp_body)
3851
3998
 
3852
- def _make_upload_request(self, request, opener):
3999
+ def _make_upload_request(
4000
+ self, request, opener: "urllib.request.OpenerDirector"
4001
+ ) -> "urllib.request._UrlopenRet":
3853
4002
  """
3854
4003
  Open the given request object, return the
3855
4004
  response, raises URLError on protocol errors.
@@ -3861,7 +4010,7 @@ class Shotgun(object):
3861
4010
  raise
3862
4011
  return result
3863
4012
 
3864
- def _parse_http_status(self, status):
4013
+ def _parse_http_status(self, status: Tuple) -> None:
3865
4014
  """
3866
4015
  Parse the status returned from the http request.
3867
4016
 
@@ -3879,7 +4028,9 @@ class Shotgun(object):
3879
4028
 
3880
4029
  return
3881
4030
 
3882
- def _decode_response(self, headers, body):
4031
+ def _decode_response(
4032
+ self, headers: Dict[str, Any], body: str
4033
+ ) -> Union[str, Dict[str, Any]]:
3883
4034
  """
3884
4035
  Decode the response from the server from the wire format to
3885
4036
  a python data structure.
@@ -3900,7 +4051,7 @@ class Shotgun(object):
3900
4051
  return self._json_loads(body)
3901
4052
  return body
3902
4053
 
3903
- def _json_loads(self, body):
4054
+ def _json_loads(self, body: str) -> Any:
3904
4055
  return json.loads(body)
3905
4056
 
3906
4057
  def _response_errors(self, sg_response):
@@ -3949,7 +4100,7 @@ class Shotgun(object):
3949
4100
  raise Fault(sg_response.get("message", "Unknown Error"))
3950
4101
  return
3951
4102
 
3952
- def _visit_data(self, data, visitor):
4103
+ def _visit_data(self, data: T, visitor) -> T:
3953
4104
  """
3954
4105
  Walk the data (simple python types) and call the visitor.
3955
4106
  """
@@ -3959,17 +4110,17 @@ class Shotgun(object):
3959
4110
 
3960
4111
  recursive = self._visit_data
3961
4112
  if isinstance(data, list):
3962
- return [recursive(i, visitor) for i in data]
4113
+ return [recursive(i, visitor) for i in data] # type: ignore[return-value]
3963
4114
 
3964
4115
  if isinstance(data, tuple):
3965
- return tuple(recursive(i, visitor) for i in data)
4116
+ return tuple(recursive(i, visitor) for i in data) # type: ignore[return-value]
3966
4117
 
3967
4118
  if isinstance(data, dict):
3968
- return dict((k, recursive(v, visitor)) for k, v in data.items())
4119
+ return dict((k, recursive(v, visitor)) for k, v in data.items()) # type: ignore[return-value]
3969
4120
 
3970
4121
  return visitor(data)
3971
4122
 
3972
- def _transform_outbound(self, data):
4123
+ def _transform_outbound(self, data: T) -> T:
3973
4124
  """
3974
4125
  Transform data types or values before they are sent by the client.
3975
4126
 
@@ -4016,7 +4167,7 @@ class Shotgun(object):
4016
4167
 
4017
4168
  return self._visit_data(data, _outbound_visitor)
4018
4169
 
4019
- def _transform_inbound(self, data):
4170
+ def _transform_inbound(self, data: T) -> T:
4020
4171
  """
4021
4172
  Transforms data types or values after they are received from the server.
4022
4173
  """
@@ -4052,7 +4203,7 @@ class Shotgun(object):
4052
4203
  # ========================================================================
4053
4204
  # Connection Functions
4054
4205
 
4055
- def _get_connection(self):
4206
+ def _get_connection(self) -> Http:
4056
4207
  """
4057
4208
  Return the current connection or creates a new connection to the current server.
4058
4209
  """
@@ -4081,7 +4232,7 @@ class Shotgun(object):
4081
4232
 
4082
4233
  return self._connection
4083
4234
 
4084
- def _close_connection(self):
4235
+ def _close_connection(self) -> None:
4085
4236
  """
4086
4237
  Close the current connection.
4087
4238
  """
@@ -4100,7 +4251,7 @@ class Shotgun(object):
4100
4251
  # ========================================================================
4101
4252
  # Utility
4102
4253
 
4103
- def _parse_records(self, records):
4254
+ def _parse_records(self, records: List) -> List:
4104
4255
  """
4105
4256
  Parse 'records' returned from the api to do local modifications:
4106
4257
 
@@ -4156,7 +4307,7 @@ class Shotgun(object):
4156
4307
 
4157
4308
  return records
4158
4309
 
4159
- def _build_thumb_url(self, entity_type, entity_id):
4310
+ def _build_thumb_url(self, entity_type: str, entity_id: int) -> str:
4160
4311
  """
4161
4312
  Return the URL for the thumbnail of an entity given the entity type and the entity id.
4162
4313
 
@@ -4204,8 +4355,12 @@ class Shotgun(object):
4204
4355
  raise RuntimeError("Unknown code %s %s" % (code, thumb_url))
4205
4356
 
4206
4357
  def _dict_to_list(
4207
- self, d, key_name="field_name", value_name="value", extra_data=None
4208
- ):
4358
+ self,
4359
+ d: Optional[Dict[str, Any]],
4360
+ key_name: str = "field_name",
4361
+ value_name: str = "value",
4362
+ extra_data=None,
4363
+ ) -> List[Dict[str, Any]]:
4209
4364
  """
4210
4365
  Utility function to convert a dict into a list dicts using the key_name and value_name keys.
4211
4366
 
@@ -4222,7 +4377,9 @@ class Shotgun(object):
4222
4377
  ret.append(d)
4223
4378
  return ret
4224
4379
 
4225
- def _dict_to_extra_data(self, d, key_name="value"):
4380
+ def _dict_to_extra_data(
4381
+ self, d: Optional[Dict[str, Any]], key_name="value"
4382
+ ) -> Dict[str, Any]:
4226
4383
  """
4227
4384
  Utility function to convert a dict into a dict compatible with the extra_data arg
4228
4385
  of _dict_to_list.
@@ -4231,7 +4388,7 @@ class Shotgun(object):
4231
4388
  """
4232
4389
  return dict([(k, {key_name: v}) for (k, v) in (d or {}).items()])
4233
4390
 
4234
- def _upload_file_to_storage(self, path, storage_url):
4391
+ def _upload_file_to_storage(self, path: str, storage_url: str) -> None:
4235
4392
  """
4236
4393
  Internal function to upload an entire file to the Cloud storage.
4237
4394
 
@@ -4251,7 +4408,9 @@ class Shotgun(object):
4251
4408
 
4252
4409
  LOG.debug("File uploaded to Cloud storage: %s", filename)
4253
4410
 
4254
- def _multipart_upload_file_to_storage(self, path, upload_info):
4411
+ def _multipart_upload_file_to_storage(
4412
+ self, path: str, upload_info: Dict[str, Any]
4413
+ ) -> None:
4255
4414
  """
4256
4415
  Internal function to upload a file to the Cloud storage in multiple parts.
4257
4416
 
@@ -4293,7 +4452,9 @@ class Shotgun(object):
4293
4452
 
4294
4453
  LOG.debug("File uploaded in multiple parts to Cloud storage: %s", path)
4295
4454
 
4296
- def _get_upload_part_link(self, upload_info, filename, part_number):
4455
+ def _get_upload_part_link(
4456
+ self, upload_info: Dict[str, Any], filename: str, part_number: int
4457
+ ) -> str:
4297
4458
  """
4298
4459
  Internal function to get the url to upload the next part of a file to the
4299
4460
  Cloud storage, in a multi-part upload process.
@@ -4333,7 +4494,9 @@ class Shotgun(object):
4333
4494
  LOG.debug("Got next upload link from server for multipart upload.")
4334
4495
  return result.split("\n", 2)[1]
4335
4496
 
4336
- def _upload_data_to_storage(self, data, content_type, size, storage_url):
4497
+ def _upload_data_to_storage(
4498
+ self, data: BinaryIO, content_type: str, size: int, storage_url: str
4499
+ ) -> str:
4337
4500
  """
4338
4501
  Internal function to upload data to Cloud storage.
4339
4502
 
@@ -4382,19 +4545,21 @@ class Shotgun(object):
4382
4545
  else:
4383
4546
  break
4384
4547
  else:
4385
- raise ShotgunError("Max attemps limit reached.")
4548
+ raise ShotgunError("Max attempts limit reached.")
4386
4549
 
4387
4550
  etag = result.info()["Etag"]
4388
4551
  LOG.debug("Part upload completed successfully.")
4389
4552
  return etag
4390
4553
 
4391
- def _complete_multipart_upload(self, upload_info, filename, etags):
4554
+ def _complete_multipart_upload(
4555
+ self, upload_info: Dict[str, Any], filename: str, etags: Iterable[str]
4556
+ ) -> None:
4392
4557
  """
4393
4558
  Internal function to complete a multi-part upload to the Cloud storage.
4394
4559
 
4395
4560
  :param dict upload_info: Contains details received from the server, about the upload.
4396
4561
  :param str filename: Name of the file for which we want to complete the upload.
4397
- :param tupple etags: Contains the etag of each uploaded file part.
4562
+ :param tuple etags: Contains the etag of each uploaded file part.
4398
4563
  """
4399
4564
 
4400
4565
  params = {
@@ -4421,7 +4586,9 @@ class Shotgun(object):
4421
4586
  if not result.startswith("1"):
4422
4587
  raise ShotgunError("Unable get upload part link: %s" % result)
4423
4588
 
4424
- def _requires_direct_s3_upload(self, entity_type, field_name):
4589
+ def _requires_direct_s3_upload(
4590
+ self, entity_type: str, field_name: Optional[str]
4591
+ ) -> bool:
4425
4592
  """
4426
4593
  Internal function that determines if an entity_type + field_name combination
4427
4594
  should be uploaded to cloud storage.
@@ -4462,7 +4629,7 @@ class Shotgun(object):
4462
4629
  else:
4463
4630
  return False
4464
4631
 
4465
- def _send_form(self, url, params):
4632
+ def _send_form(self, url: str, params: Dict[str, Any]) -> str:
4466
4633
  """
4467
4634
  Utility function to send a Form to Shotgun and process any HTTP errors that
4468
4635
  could occur.
@@ -4502,42 +4669,7 @@ class Shotgun(object):
4502
4669
 
4503
4670
  return result
4504
4671
  else:
4505
- raise ShotgunError("Max attemps limit reached.")
4506
-
4507
-
4508
- class CACertsHTTPSConnection(http.client.HTTPConnection):
4509
- """ "
4510
- This class allows to create an HTTPS connection that uses the custom certificates
4511
- passed in.
4512
- """
4513
-
4514
- default_port = http.client.HTTPS_PORT
4515
-
4516
- def __init__(self, *args, **kwargs):
4517
- """
4518
- :param args: Positional arguments passed down to the base class.
4519
- :param ca_certs: Path to the custom CA certs file.
4520
- :param kwargs: Keyword arguments passed down to the bas class
4521
- """
4522
- # Pop that argument,
4523
- self.__ca_certs = kwargs.pop("ca_certs")
4524
- super().__init__(self, *args, **kwargs)
4525
-
4526
- def connect(self):
4527
- "Connect to a host on a given (SSL) port."
4528
- super().connect(self)
4529
- # Now that the regular HTTP socket has been created, wrap it with our SSL certs.
4530
- if (sys.version_info.major, sys.version_info.minor) >= (3, 8):
4531
- context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
4532
- context.verify_mode = ssl.CERT_REQUIRED
4533
- context.check_hostname = False
4534
- if self.__ca_certs:
4535
- context.load_verify_locations(self.__ca_certs)
4536
- self.sock = context.wrap_socket(self.sock)
4537
- else:
4538
- self.sock = ssl.wrap_socket(
4539
- self.sock, ca_certs=self.__ca_certs, cert_reqs=ssl.CERT_REQUIRED
4540
- )
4672
+ raise ShotgunError("Max attempts limit reached.")
4541
4673
 
4542
4674
 
4543
4675
  # Helpers from the previous API, left as is.
@@ -4629,7 +4761,7 @@ class FormPostHandler(urllib.request.BaseHandler):
4629
4761
  return self.http_request(request)
4630
4762
 
4631
4763
 
4632
- def _translate_filters(filters, filter_operator):
4764
+ def _translate_filters(filters: Union[List, Tuple], filter_operator) -> Dict[str, Any]:
4633
4765
  """
4634
4766
  Translate filters params into data structure expected by rpc call.
4635
4767
  """
@@ -4638,7 +4770,7 @@ def _translate_filters(filters, filter_operator):
4638
4770
  return _translate_filters_dict(wrapped_filters)
4639
4771
 
4640
4772
 
4641
- def _translate_filters_dict(sg_filter):
4773
+ def _translate_filters_dict(sg_filter: Dict[str, Any]) -> Dict[str, Any]:
4642
4774
  new_filters = {}
4643
4775
  filter_operator = sg_filter.get("filter_operator")
4644
4776
 
@@ -4691,31 +4823,44 @@ def _translate_filters_simple(sg_filter):
4691
4823
  and condition["relation"] in ["is", "is_not", "in", "not_in"]
4692
4824
  and isinstance(values[0], dict)
4693
4825
  ):
4694
- values = [_get_type_and_id_from_value(v) for v in values]
4826
+ values = [_optimize_filter_field(v) for v in values]
4695
4827
 
4696
4828
  condition["values"] = values
4697
4829
 
4698
4830
  return condition
4699
4831
 
4700
4832
 
4701
- def _version_str(version):
4833
+ def _version_str(version) -> str:
4702
4834
  """
4703
4835
  Convert a tuple of int's to a '.' separated str.
4704
4836
  """
4705
4837
  return ".".join(map(str, version))
4706
4838
 
4707
4839
 
4708
- def _get_type_and_id_from_value(value):
4840
+ def _optimize_filter_field(
4841
+ field_value: Union[Dict[str, Any], List], recursive: bool = True
4842
+ ) -> Union[Dict, List]:
4709
4843
  """
4710
- For an entity dictionary, returns a new dictionary with only the type and id keys.
4711
- If any of these keys are not present, the original dictionary is returned.
4844
+ For an FPT entity, returns a new dictionary with only the type,
4845
+ id, and other allowed keys.
4846
+ If case of any processing error, the original dictionary is returned.
4847
+
4848
+ At least `type` and `id` keys are required to do the optimization
4712
4849
  """
4713
- try:
4714
- if isinstance(value, dict):
4715
- return {"type": value["type"], "id": value["id"]}
4716
- elif isinstance(value, list):
4717
- return [{"type": v["type"], "id": v["id"]} for v in value]
4718
- except (KeyError, TypeError):
4719
- LOG.debug(f"Could not optimize entity value {value}")
4720
-
4721
- return value
4850
+ allowed_keys = {
4851
+ "id",
4852
+ "type",
4853
+ "url",
4854
+ "name",
4855
+ "content_type",
4856
+ "local_path",
4857
+ "storage",
4858
+ "relative_path",
4859
+ }
4860
+ if isinstance(field_value, dict) and "id" in field_value and "type" in field_value:
4861
+ return {key: field_value[key] for key in allowed_keys if key in field_value}
4862
+
4863
+ elif recursive and isinstance(field_value, list):
4864
+ return [_optimize_filter_field(fv, recursive=False) for fv in field_value]
4865
+
4866
+ return field_value