shotgun-api3 3.9.0__py2.py3-none-any.whl → 3.9.1__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.1"
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,7 @@ 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 follow(self, user: Dict[str, Any], entity: Dict[str, Any]) -> Dict[str, Any]:
1830
1886
  """
1831
1887
  Add the entity to the user's followed entities.
1832
1888
 
@@ -1854,7 +1910,7 @@ class Shotgun(object):
1854
1910
 
1855
1911
  return self._call_rpc("follow", params)
1856
1912
 
1857
- def unfollow(self, user, entity):
1913
+ def unfollow(self, user: Dict[str, Any], entity: Dict[str, Any]) -> Dict[str, Any]:
1858
1914
  """
1859
1915
  Remove entity from the user's followed entities.
1860
1916
 
@@ -1881,7 +1937,7 @@ class Shotgun(object):
1881
1937
 
1882
1938
  return self._call_rpc("unfollow", params)
1883
1939
 
1884
- def followers(self, entity):
1940
+ def followers(self, entity: Dict[str, Any]) -> List[Dict[str, Any]]:
1885
1941
  """
1886
1942
  Return all followers for an entity.
1887
1943
 
@@ -1909,7 +1965,12 @@ class Shotgun(object):
1909
1965
 
1910
1966
  return self._call_rpc("followers", params)
1911
1967
 
1912
- def following(self, user, project=None, entity_type=None):
1968
+ def following(
1969
+ self,
1970
+ user: Dict[str, Any],
1971
+ project: Optional[Dict[str, Any]] = None,
1972
+ entity_type: Optional[str] = None,
1973
+ ) -> List[BaseEntity]:
1913
1974
  """
1914
1975
  Return all entity instances a user is following.
1915
1976
 
@@ -1940,7 +2001,9 @@ class Shotgun(object):
1940
2001
 
1941
2002
  return self._call_rpc("following", params)
1942
2003
 
1943
- def schema_entity_read(self, project_entity=None):
2004
+ def schema_entity_read(
2005
+ self, project_entity: Optional[BaseEntity] = None
2006
+ ) -> Dict[str, Dict[str, Any]]:
1944
2007
  """
1945
2008
  Return all active entity types, their display names, and their visibility.
1946
2009
 
@@ -1975,7 +2038,7 @@ class Shotgun(object):
1975
2038
  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
2039
  """
1977
2040
 
1978
- params = {}
2041
+ params: Dict[str, Any] = {}
1979
2042
 
1980
2043
  params = self._add_project_param(params, project_entity)
1981
2044
 
@@ -1984,7 +2047,9 @@ class Shotgun(object):
1984
2047
  else:
1985
2048
  return self._call_rpc("schema_entity_read", None)
1986
2049
 
1987
- def schema_read(self, project_entity=None):
2050
+ def schema_read(
2051
+ self, project_entity: Optional[BaseEntity] = None
2052
+ ) -> Dict[str, Dict[str, Any]]:
1988
2053
  """
1989
2054
  Get the schema for all fields on all entities.
1990
2055
 
@@ -2047,7 +2112,7 @@ class Shotgun(object):
2047
2112
  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
2113
  """
2049
2114
 
2050
- params = {}
2115
+ params: Dict[str, Any] = {}
2051
2116
 
2052
2117
  params = self._add_project_param(params, project_entity)
2053
2118
 
@@ -2056,7 +2121,12 @@ class Shotgun(object):
2056
2121
  else:
2057
2122
  return self._call_rpc("schema_read", None)
2058
2123
 
2059
- def schema_field_read(self, entity_type, field_name=None, project_entity=None):
2124
+ def schema_field_read(
2125
+ self,
2126
+ entity_type: str,
2127
+ field_name: Optional[str] = None,
2128
+ project_entity: Optional[BaseEntity] = None,
2129
+ ) -> Dict[str, Dict[str, Any]]:
2060
2130
  """
2061
2131
  Get schema for all fields on the specified entity type or just the field name specified
2062
2132
  if provided.
@@ -2121,8 +2191,12 @@ class Shotgun(object):
2121
2191
  return self._call_rpc("schema_field_read", params)
2122
2192
 
2123
2193
  def schema_field_create(
2124
- self, entity_type, data_type, display_name, properties=None
2125
- ):
2194
+ self,
2195
+ entity_type: str,
2196
+ data_type: str,
2197
+ display_name: str,
2198
+ properties: Optional[Dict[str, Any]] = None,
2199
+ ) -> str:
2126
2200
  """
2127
2201
  Create a field for the specified entity type.
2128
2202
 
@@ -2160,8 +2234,12 @@ class Shotgun(object):
2160
2234
  return self._call_rpc("schema_field_create", params)
2161
2235
 
2162
2236
  def schema_field_update(
2163
- self, entity_type, field_name, properties, project_entity=None
2164
- ):
2237
+ self,
2238
+ entity_type: str,
2239
+ field_name: str,
2240
+ properties: Dict[str, Any],
2241
+ project_entity: Optional[BaseEntity] = None,
2242
+ ) -> bool:
2165
2243
  """
2166
2244
  Update the properties for the specified field on an entity.
2167
2245
 
@@ -2175,9 +2253,9 @@ class Shotgun(object):
2175
2253
  >>> sg.schema_field_update("Asset", "sg_test_number", properties)
2176
2254
  True
2177
2255
 
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
2256
+ :param str entity_type: Entity type of field to update.
2257
+ :param str field_name: Internal Shotgun name of the field to update.
2258
+ :param dict properties: Dictionary with key/value pairs where the key is the property to be
2181
2259
  updated and the value is the new value.
2182
2260
  :param dict project_entity: Optional Project entity specifying which project to modify the
2183
2261
  ``visible`` property for. If ``visible`` is present in ``properties`` and
@@ -2202,7 +2280,7 @@ class Shotgun(object):
2202
2280
  params = self._add_project_param(params, project_entity)
2203
2281
  return self._call_rpc("schema_field_update", params)
2204
2282
 
2205
- def schema_field_delete(self, entity_type, field_name):
2283
+ def schema_field_delete(self, entity_type: str, field_name: str) -> bool:
2206
2284
  """
2207
2285
  Delete the specified field from the entity type.
2208
2286
 
@@ -2219,7 +2297,7 @@ class Shotgun(object):
2219
2297
 
2220
2298
  return self._call_rpc("schema_field_delete", params)
2221
2299
 
2222
- def add_user_agent(self, agent):
2300
+ def add_user_agent(self, agent: str) -> None:
2223
2301
  """
2224
2302
  Add agent to the user-agent header.
2225
2303
 
@@ -2231,7 +2309,7 @@ class Shotgun(object):
2231
2309
  """
2232
2310
  self._user_agents.append(agent)
2233
2311
 
2234
- def reset_user_agent(self):
2312
+ def reset_user_agent(self) -> None:
2235
2313
  """
2236
2314
  Reset user agent to the default value.
2237
2315
 
@@ -2251,7 +2329,7 @@ class Shotgun(object):
2251
2329
  "ssl %s" % (self.client_caps.ssl_version),
2252
2330
  ]
2253
2331
 
2254
- def set_session_uuid(self, session_uuid):
2332
+ def set_session_uuid(self, session_uuid: str) -> None:
2255
2333
  """
2256
2334
  Set the browser session_uuid in the current Shotgun API instance.
2257
2335
 
@@ -2269,12 +2347,12 @@ class Shotgun(object):
2269
2347
 
2270
2348
  def share_thumbnail(
2271
2349
  self,
2272
- entities,
2273
- thumbnail_path=None,
2274
- source_entity=None,
2275
- filmstrip_thumbnail=False,
2276
- **kwargs,
2277
- ):
2350
+ entities: List[Dict[str, Any]],
2351
+ thumbnail_path: Optional[str] = None,
2352
+ source_entity: Optional[BaseEntity] = None,
2353
+ filmstrip_thumbnail: bool = False,
2354
+ **kwargs: Any,
2355
+ ) -> int:
2278
2356
  """
2279
2357
  Associate a thumbnail with more than one Shotgun entity.
2280
2358
 
@@ -2413,7 +2491,9 @@ class Shotgun(object):
2413
2491
 
2414
2492
  return attachment_id
2415
2493
 
2416
- def upload_thumbnail(self, entity_type, entity_id, path, **kwargs):
2494
+ def upload_thumbnail(
2495
+ self, entity_type: str, entity_id: int, path: str, **kwargs: Any
2496
+ ) -> int:
2417
2497
  """
2418
2498
  Upload a file from a local path and assign it as the thumbnail for the specified entity.
2419
2499
 
@@ -2438,12 +2518,15 @@ class Shotgun(object):
2438
2518
  :param int entity_id: Id of the entity to set the thumbnail for.
2439
2519
  :param str path: Full path to the thumbnail file on disk.
2440
2520
  :returns: Id of the new attachment
2521
+ :rtype: int
2441
2522
  """
2442
2523
  return self.upload(
2443
2524
  entity_type, entity_id, path, field_name="thumb_image", **kwargs
2444
2525
  )
2445
2526
 
2446
- def upload_filmstrip_thumbnail(self, entity_type, entity_id, path, **kwargs):
2527
+ def upload_filmstrip_thumbnail(
2528
+ self, entity_type: str, entity_id: int, path: str, **kwargs: Any
2529
+ ) -> int:
2447
2530
  """
2448
2531
  Upload filmstrip thumbnail to specified entity.
2449
2532
 
@@ -2494,13 +2577,13 @@ class Shotgun(object):
2494
2577
 
2495
2578
  def upload(
2496
2579
  self,
2497
- entity_type,
2498
- entity_id,
2499
- path,
2500
- field_name=None,
2501
- display_name=None,
2502
- tag_list=None,
2503
- ):
2580
+ entity_type: str,
2581
+ entity_id: int,
2582
+ path: str,
2583
+ field_name: Optional[str] = None,
2584
+ display_name: Optional[str] = None,
2585
+ tag_list: Optional[str] = None,
2586
+ ) -> int:
2504
2587
  """
2505
2588
  Upload a file to the specified entity.
2506
2589
 
@@ -2583,14 +2666,14 @@ class Shotgun(object):
2583
2666
 
2584
2667
  def _upload_to_storage(
2585
2668
  self,
2586
- entity_type,
2587
- entity_id,
2588
- path,
2589
- field_name,
2590
- display_name,
2591
- tag_list,
2592
- is_thumbnail,
2593
- ):
2669
+ entity_type: str,
2670
+ entity_id: int,
2671
+ path: str,
2672
+ field_name: Optional[str],
2673
+ display_name: Optional[str],
2674
+ tag_list: Optional[str],
2675
+ is_thumbnail: bool,
2676
+ ) -> int:
2594
2677
  """
2595
2678
  Internal function to upload a file to the Cloud storage and link it to the specified entity.
2596
2679
 
@@ -2673,14 +2756,14 @@ class Shotgun(object):
2673
2756
 
2674
2757
  def _upload_to_sg(
2675
2758
  self,
2676
- entity_type,
2677
- entity_id,
2678
- path,
2679
- field_name,
2680
- display_name,
2681
- tag_list,
2682
- is_thumbnail,
2683
- ):
2759
+ entity_type: str,
2760
+ entity_id: int,
2761
+ path: str,
2762
+ field_name: Optional[str],
2763
+ display_name: Optional[str],
2764
+ tag_list: Optional[str],
2765
+ is_thumbnail: bool,
2766
+ ) -> int:
2684
2767
  """
2685
2768
  Internal function to upload a file to Shotgun and link it to the specified entity.
2686
2769
 
@@ -2752,7 +2835,9 @@ class Shotgun(object):
2752
2835
  attachment_id = int(result.split(":", 2)[1].split("\n", 1)[0])
2753
2836
  return attachment_id
2754
2837
 
2755
- def _get_attachment_upload_info(self, is_thumbnail, filename, is_multipart_upload):
2838
+ def _get_attachment_upload_info(
2839
+ self, is_thumbnail: bool, filename: str, is_multipart_upload: bool
2840
+ ) -> Dict[str, Any]:
2756
2841
  """
2757
2842
  Internal function to get the information needed to upload a file to Cloud storage.
2758
2843
 
@@ -2799,7 +2884,12 @@ class Shotgun(object):
2799
2884
  "upload_info": upload_info,
2800
2885
  }
2801
2886
 
2802
- def download_attachment(self, attachment=False, file_path=None, attachment_id=None):
2887
+ def download_attachment(
2888
+ self,
2889
+ attachment: Union[Dict[str, Any], bool] = False,
2890
+ file_path: Optional[str] = None,
2891
+ attachment_id: Optional[int] = None,
2892
+ ) -> Union[str, bytes, None]:
2803
2893
  """
2804
2894
  Download the file associated with a Shotgun Attachment.
2805
2895
 
@@ -2915,7 +3005,7 @@ class Shotgun(object):
2915
3005
  else:
2916
3006
  return attachment
2917
3007
 
2918
- def get_auth_cookie_handler(self):
3008
+ def get_auth_cookie_handler(self) -> urllib.request.HTTPCookieProcessor:
2919
3009
  """
2920
3010
  Return an urllib cookie handler containing a cookie for FPTR
2921
3011
  authentication.
@@ -2947,7 +3037,9 @@ class Shotgun(object):
2947
3037
  cj.set_cookie(c)
2948
3038
  return urllib.request.HTTPCookieProcessor(cj)
2949
3039
 
2950
- def get_attachment_download_url(self, attachment):
3040
+ def get_attachment_download_url(
3041
+ self, attachment: Optional[Union[int, dict[str, Any]]]
3042
+ ) -> str:
2951
3043
  """
2952
3044
  Return the URL for downloading provided Attachment.
2953
3045
 
@@ -3005,7 +3097,9 @@ class Shotgun(object):
3005
3097
  )
3006
3098
  return url
3007
3099
 
3008
- def authenticate_human_user(self, user_login, user_password, auth_token=None):
3100
+ def authenticate_human_user(
3101
+ self, user_login: str, user_password: str, auth_token: Optional[str] = None
3102
+ ) -> Union[Dict[str, Any], None]:
3009
3103
  """
3010
3104
  Authenticate Shotgun HumanUser.
3011
3105
 
@@ -3064,7 +3158,9 @@ class Shotgun(object):
3064
3158
  self.config.auth_token = original_auth_token
3065
3159
  raise
3066
3160
 
3067
- def update_project_last_accessed(self, project, user=None):
3161
+ def update_project_last_accessed(
3162
+ self, project: Dict[str, Any], user: Optional[Dict[str, Any]] = None
3163
+ ) -> None:
3068
3164
  """
3069
3165
  Update a Project's ``last_accessed_by_current_user`` field to the current timestamp.
3070
3166
 
@@ -3110,7 +3206,9 @@ class Shotgun(object):
3110
3206
  record = self._call_rpc("update_project_last_accessed_by_current_user", params)
3111
3207
  self._parse_records(record)[0]
3112
3208
 
3113
- def note_thread_read(self, note_id, entity_fields=None):
3209
+ def note_thread_read(
3210
+ self, note_id: int, entity_fields: Optional[Dict[str, Any]] = None
3211
+ ) -> List[Dict[str, Any]]:
3114
3212
  """
3115
3213
  Return the full conversation for a given note, including Replies and Attachments.
3116
3214
 
@@ -3185,7 +3283,13 @@ class Shotgun(object):
3185
3283
  result = self._parse_records(record)
3186
3284
  return result
3187
3285
 
3188
- def text_search(self, text, entity_types, project_ids=None, limit=None):
3286
+ def text_search(
3287
+ self,
3288
+ text: str,
3289
+ entity_types: Dict[str, Any],
3290
+ project_ids: Optional[List] = None,
3291
+ limit: Optional[int] = None,
3292
+ ) -> Dict[str, Any]:
3189
3293
  """
3190
3294
  Search across the specified entity types for the given text.
3191
3295
 
@@ -3279,13 +3383,13 @@ class Shotgun(object):
3279
3383
 
3280
3384
  def activity_stream_read(
3281
3385
  self,
3282
- entity_type,
3283
- entity_id,
3284
- entity_fields=None,
3285
- min_id=None,
3286
- max_id=None,
3287
- limit=None,
3288
- ):
3386
+ entity_type: str,
3387
+ entity_id: int,
3388
+ entity_fields: Optional[Dict[str, Any]] = None,
3389
+ min_id: Optional[int] = None,
3390
+ max_id: Optional[int] = None,
3391
+ limit: Optional[int] = None,
3392
+ ) -> Dict[str, Any]:
3289
3393
  """
3290
3394
  Retrieve activity stream data from Shotgun.
3291
3395
 
@@ -3375,7 +3479,7 @@ class Shotgun(object):
3375
3479
  result = self._parse_records(record)[0]
3376
3480
  return result
3377
3481
 
3378
- def nav_expand(self, path, seed_entity_field=None, entity_fields=None):
3482
+ def nav_expand(self, path: str, seed_entity_field=None, entity_fields=None):
3379
3483
  """
3380
3484
  Expand the navigation hierarchy for the supplied path.
3381
3485
 
@@ -3395,7 +3499,9 @@ class Shotgun(object):
3395
3499
  },
3396
3500
  )
3397
3501
 
3398
- def nav_search_string(self, root_path, search_string, seed_entity_field=None):
3502
+ def nav_search_string(
3503
+ self, root_path: str, search_string: str, seed_entity_field=None
3504
+ ):
3399
3505
  """
3400
3506
  Search function adapted to work with the navigation hierarchy.
3401
3507
 
@@ -3414,7 +3520,12 @@ class Shotgun(object):
3414
3520
  },
3415
3521
  )
3416
3522
 
3417
- def nav_search_entity(self, root_path, entity, seed_entity_field=None):
3523
+ def nav_search_entity(
3524
+ self,
3525
+ root_path: str,
3526
+ entity: Dict[str, Any],
3527
+ seed_entity_field: Optional[Dict[str, Any]] = None,
3528
+ ):
3418
3529
  """
3419
3530
  Search function adapted to work with the navigation hierarchy.
3420
3531
 
@@ -3434,7 +3545,7 @@ class Shotgun(object):
3434
3545
  },
3435
3546
  )
3436
3547
 
3437
- def get_session_token(self):
3548
+ def get_session_token(self) -> str:
3438
3549
  """
3439
3550
  Get the session token associated with the current session.
3440
3551
 
@@ -3458,7 +3569,7 @@ class Shotgun(object):
3458
3569
 
3459
3570
  return session_token
3460
3571
 
3461
- def preferences_read(self, prefs=None):
3572
+ def preferences_read(self, prefs: Optional[List] = None) -> Dict[str, Any]:
3462
3573
  """
3463
3574
  Get a subset of the site preferences.
3464
3575
 
@@ -3481,7 +3592,7 @@ class Shotgun(object):
3481
3592
 
3482
3593
  return self._call_rpc("preferences_read", {"prefs": prefs})
3483
3594
 
3484
- def user_subscriptions_read(self):
3595
+ def user_subscriptions_read(self) -> List:
3485
3596
  """
3486
3597
  Get the list of user subscriptions.
3487
3598
 
@@ -3493,8 +3604,9 @@ class Shotgun(object):
3493
3604
 
3494
3605
  return self._call_rpc("user_subscriptions_read", None)
3495
3606
 
3496
- def user_subscriptions_create(self, users):
3497
- # type: (list[dict[str, Union[str, list[str], None]) -> bool
3607
+ def user_subscriptions_create(
3608
+ self, users: List[Dict[str, Union[str, List[str], None]]]
3609
+ ) -> bool:
3498
3610
  """
3499
3611
  Assign subscriptions to users.
3500
3612
 
@@ -3515,7 +3627,7 @@ class Shotgun(object):
3515
3627
 
3516
3628
  return response.get("status") == "success"
3517
3629
 
3518
- def _build_opener(self, handler):
3630
+ def _build_opener(self, handler) -> urllib.request.OpenerDirector:
3519
3631
  """
3520
3632
  Build urllib2 opener with appropriate proxy handler.
3521
3633
  """
@@ -3616,7 +3728,13 @@ class Shotgun(object):
3616
3728
  # ========================================================================
3617
3729
  # RPC Functions
3618
3730
 
3619
- def _call_rpc(self, method, params, include_auth_params=True, first=False):
3731
+ def _call_rpc(
3732
+ self,
3733
+ method: str,
3734
+ params: Any,
3735
+ include_auth_params: bool = True,
3736
+ first: bool = False,
3737
+ ) -> Any:
3620
3738
  """
3621
3739
  Call the specified method on the Shotgun Server sending the supplied payload.
3622
3740
  """
@@ -3680,7 +3798,7 @@ class Shotgun(object):
3680
3798
  return results[0]
3681
3799
  return results
3682
3800
 
3683
- def _auth_params(self):
3801
+ def _auth_params(self) -> Dict[str, Any]:
3684
3802
  """
3685
3803
  Return a dictionary of the authentication parameters being used.
3686
3804
  """
@@ -3735,7 +3853,7 @@ class Shotgun(object):
3735
3853
 
3736
3854
  return auth_params
3737
3855
 
3738
- def _sanitize_auth_params(self, params):
3856
+ def _sanitize_auth_params(self, params: Dict[str, Any]) -> Dict[str, Any]:
3739
3857
  """
3740
3858
  Given an authentication parameter dictionary, sanitize any sensitive
3741
3859
  information and return the sanitized dict copy.
@@ -3746,7 +3864,9 @@ class Shotgun(object):
3746
3864
  sanitized_params[k] = "********"
3747
3865
  return sanitized_params
3748
3866
 
3749
- def _build_payload(self, method, params, include_auth_params=True):
3867
+ def _build_payload(
3868
+ self, method: str, params, include_auth_params: bool = True
3869
+ ) -> Dict[str, Any]:
3750
3870
  """
3751
3871
  Build the payload to be send to the rpc endpoint.
3752
3872
  """
@@ -3764,7 +3884,7 @@ class Shotgun(object):
3764
3884
 
3765
3885
  return {"method_name": method, "params": call_params}
3766
3886
 
3767
- def _encode_payload(self, payload):
3887
+ def _encode_payload(self, payload) -> bytes:
3768
3888
  """
3769
3889
  Encode the payload to a string to be passed to the rpc endpoint.
3770
3890
 
@@ -3775,7 +3895,9 @@ class Shotgun(object):
3775
3895
 
3776
3896
  return json.dumps(payload, ensure_ascii=False).encode("utf-8")
3777
3897
 
3778
- def _make_call(self, verb, path, body, headers):
3898
+ def _make_call(
3899
+ self, verb: str, path: str, body, headers: Optional[Dict[str, Any]]
3900
+ ) -> Tuple[Tuple[int, str], Dict[str, Any], str]:
3779
3901
  """
3780
3902
  Make an HTTP call to the server.
3781
3903
 
@@ -3825,7 +3947,9 @@ class Shotgun(object):
3825
3947
  )
3826
3948
  time.sleep(rpc_attempt_interval)
3827
3949
 
3828
- def _http_request(self, verb, path, body, headers):
3950
+ def _http_request(
3951
+ self, verb: str, path: str, body, headers: Dict[str, Any]
3952
+ ) -> Tuple[Tuple[int, str], Dict[str, Any], str]:
3829
3953
  """
3830
3954
  Make the actual HTTP request.
3831
3955
  """
@@ -3849,7 +3973,9 @@ class Shotgun(object):
3849
3973
 
3850
3974
  return (http_status, resp_headers, resp_body)
3851
3975
 
3852
- def _make_upload_request(self, request, opener):
3976
+ def _make_upload_request(
3977
+ self, request, opener: "urllib.request.OpenerDirector"
3978
+ ) -> "urllib.request._UrlopenRet":
3853
3979
  """
3854
3980
  Open the given request object, return the
3855
3981
  response, raises URLError on protocol errors.
@@ -3861,7 +3987,7 @@ class Shotgun(object):
3861
3987
  raise
3862
3988
  return result
3863
3989
 
3864
- def _parse_http_status(self, status):
3990
+ def _parse_http_status(self, status: Tuple) -> None:
3865
3991
  """
3866
3992
  Parse the status returned from the http request.
3867
3993
 
@@ -3879,7 +4005,9 @@ class Shotgun(object):
3879
4005
 
3880
4006
  return
3881
4007
 
3882
- def _decode_response(self, headers, body):
4008
+ def _decode_response(
4009
+ self, headers: Dict[str, Any], body: str
4010
+ ) -> Union[str, Dict[str, Any]]:
3883
4011
  """
3884
4012
  Decode the response from the server from the wire format to
3885
4013
  a python data structure.
@@ -3900,7 +4028,7 @@ class Shotgun(object):
3900
4028
  return self._json_loads(body)
3901
4029
  return body
3902
4030
 
3903
- def _json_loads(self, body):
4031
+ def _json_loads(self, body: str) -> Any:
3904
4032
  return json.loads(body)
3905
4033
 
3906
4034
  def _response_errors(self, sg_response):
@@ -3949,7 +4077,7 @@ class Shotgun(object):
3949
4077
  raise Fault(sg_response.get("message", "Unknown Error"))
3950
4078
  return
3951
4079
 
3952
- def _visit_data(self, data, visitor):
4080
+ def _visit_data(self, data: T, visitor) -> T:
3953
4081
  """
3954
4082
  Walk the data (simple python types) and call the visitor.
3955
4083
  """
@@ -3959,17 +4087,17 @@ class Shotgun(object):
3959
4087
 
3960
4088
  recursive = self._visit_data
3961
4089
  if isinstance(data, list):
3962
- return [recursive(i, visitor) for i in data]
4090
+ return [recursive(i, visitor) for i in data] # type: ignore[return-value]
3963
4091
 
3964
4092
  if isinstance(data, tuple):
3965
- return tuple(recursive(i, visitor) for i in data)
4093
+ return tuple(recursive(i, visitor) for i in data) # type: ignore[return-value]
3966
4094
 
3967
4095
  if isinstance(data, dict):
3968
- return dict((k, recursive(v, visitor)) for k, v in data.items())
4096
+ return dict((k, recursive(v, visitor)) for k, v in data.items()) # type: ignore[return-value]
3969
4097
 
3970
4098
  return visitor(data)
3971
4099
 
3972
- def _transform_outbound(self, data):
4100
+ def _transform_outbound(self, data: T) -> T:
3973
4101
  """
3974
4102
  Transform data types or values before they are sent by the client.
3975
4103
 
@@ -4016,7 +4144,7 @@ class Shotgun(object):
4016
4144
 
4017
4145
  return self._visit_data(data, _outbound_visitor)
4018
4146
 
4019
- def _transform_inbound(self, data):
4147
+ def _transform_inbound(self, data: T) -> T:
4020
4148
  """
4021
4149
  Transforms data types or values after they are received from the server.
4022
4150
  """
@@ -4052,7 +4180,7 @@ class Shotgun(object):
4052
4180
  # ========================================================================
4053
4181
  # Connection Functions
4054
4182
 
4055
- def _get_connection(self):
4183
+ def _get_connection(self) -> Http:
4056
4184
  """
4057
4185
  Return the current connection or creates a new connection to the current server.
4058
4186
  """
@@ -4081,7 +4209,7 @@ class Shotgun(object):
4081
4209
 
4082
4210
  return self._connection
4083
4211
 
4084
- def _close_connection(self):
4212
+ def _close_connection(self) -> None:
4085
4213
  """
4086
4214
  Close the current connection.
4087
4215
  """
@@ -4100,7 +4228,7 @@ class Shotgun(object):
4100
4228
  # ========================================================================
4101
4229
  # Utility
4102
4230
 
4103
- def _parse_records(self, records):
4231
+ def _parse_records(self, records: List) -> List:
4104
4232
  """
4105
4233
  Parse 'records' returned from the api to do local modifications:
4106
4234
 
@@ -4156,7 +4284,7 @@ class Shotgun(object):
4156
4284
 
4157
4285
  return records
4158
4286
 
4159
- def _build_thumb_url(self, entity_type, entity_id):
4287
+ def _build_thumb_url(self, entity_type: str, entity_id: int) -> str:
4160
4288
  """
4161
4289
  Return the URL for the thumbnail of an entity given the entity type and the entity id.
4162
4290
 
@@ -4204,8 +4332,12 @@ class Shotgun(object):
4204
4332
  raise RuntimeError("Unknown code %s %s" % (code, thumb_url))
4205
4333
 
4206
4334
  def _dict_to_list(
4207
- self, d, key_name="field_name", value_name="value", extra_data=None
4208
- ):
4335
+ self,
4336
+ d: Optional[Dict[str, Any]],
4337
+ key_name: str = "field_name",
4338
+ value_name: str = "value",
4339
+ extra_data=None,
4340
+ ) -> List[Dict[str, Any]]:
4209
4341
  """
4210
4342
  Utility function to convert a dict into a list dicts using the key_name and value_name keys.
4211
4343
 
@@ -4222,7 +4354,9 @@ class Shotgun(object):
4222
4354
  ret.append(d)
4223
4355
  return ret
4224
4356
 
4225
- def _dict_to_extra_data(self, d, key_name="value"):
4357
+ def _dict_to_extra_data(
4358
+ self, d: Optional[Dict[str, Any]], key_name="value"
4359
+ ) -> Dict[str, Any]:
4226
4360
  """
4227
4361
  Utility function to convert a dict into a dict compatible with the extra_data arg
4228
4362
  of _dict_to_list.
@@ -4231,7 +4365,7 @@ class Shotgun(object):
4231
4365
  """
4232
4366
  return dict([(k, {key_name: v}) for (k, v) in (d or {}).items()])
4233
4367
 
4234
- def _upload_file_to_storage(self, path, storage_url):
4368
+ def _upload_file_to_storage(self, path: str, storage_url: str) -> None:
4235
4369
  """
4236
4370
  Internal function to upload an entire file to the Cloud storage.
4237
4371
 
@@ -4251,7 +4385,9 @@ class Shotgun(object):
4251
4385
 
4252
4386
  LOG.debug("File uploaded to Cloud storage: %s", filename)
4253
4387
 
4254
- def _multipart_upload_file_to_storage(self, path, upload_info):
4388
+ def _multipart_upload_file_to_storage(
4389
+ self, path: str, upload_info: Dict[str, Any]
4390
+ ) -> None:
4255
4391
  """
4256
4392
  Internal function to upload a file to the Cloud storage in multiple parts.
4257
4393
 
@@ -4293,7 +4429,9 @@ class Shotgun(object):
4293
4429
 
4294
4430
  LOG.debug("File uploaded in multiple parts to Cloud storage: %s", path)
4295
4431
 
4296
- def _get_upload_part_link(self, upload_info, filename, part_number):
4432
+ def _get_upload_part_link(
4433
+ self, upload_info: Dict[str, Any], filename: str, part_number: int
4434
+ ) -> str:
4297
4435
  """
4298
4436
  Internal function to get the url to upload the next part of a file to the
4299
4437
  Cloud storage, in a multi-part upload process.
@@ -4333,7 +4471,9 @@ class Shotgun(object):
4333
4471
  LOG.debug("Got next upload link from server for multipart upload.")
4334
4472
  return result.split("\n", 2)[1]
4335
4473
 
4336
- def _upload_data_to_storage(self, data, content_type, size, storage_url):
4474
+ def _upload_data_to_storage(
4475
+ self, data: BinaryIO, content_type: str, size: int, storage_url: str
4476
+ ) -> str:
4337
4477
  """
4338
4478
  Internal function to upload data to Cloud storage.
4339
4479
 
@@ -4382,19 +4522,21 @@ class Shotgun(object):
4382
4522
  else:
4383
4523
  break
4384
4524
  else:
4385
- raise ShotgunError("Max attemps limit reached.")
4525
+ raise ShotgunError("Max attempts limit reached.")
4386
4526
 
4387
4527
  etag = result.info()["Etag"]
4388
4528
  LOG.debug("Part upload completed successfully.")
4389
4529
  return etag
4390
4530
 
4391
- def _complete_multipart_upload(self, upload_info, filename, etags):
4531
+ def _complete_multipart_upload(
4532
+ self, upload_info: Dict[str, Any], filename: str, etags: Iterable[str]
4533
+ ) -> None:
4392
4534
  """
4393
4535
  Internal function to complete a multi-part upload to the Cloud storage.
4394
4536
 
4395
4537
  :param dict upload_info: Contains details received from the server, about the upload.
4396
4538
  :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.
4539
+ :param tuple etags: Contains the etag of each uploaded file part.
4398
4540
  """
4399
4541
 
4400
4542
  params = {
@@ -4421,7 +4563,9 @@ class Shotgun(object):
4421
4563
  if not result.startswith("1"):
4422
4564
  raise ShotgunError("Unable get upload part link: %s" % result)
4423
4565
 
4424
- def _requires_direct_s3_upload(self, entity_type, field_name):
4566
+ def _requires_direct_s3_upload(
4567
+ self, entity_type: str, field_name: Optional[str]
4568
+ ) -> bool:
4425
4569
  """
4426
4570
  Internal function that determines if an entity_type + field_name combination
4427
4571
  should be uploaded to cloud storage.
@@ -4462,7 +4606,7 @@ class Shotgun(object):
4462
4606
  else:
4463
4607
  return False
4464
4608
 
4465
- def _send_form(self, url, params):
4609
+ def _send_form(self, url: str, params: Dict[str, Any]) -> str:
4466
4610
  """
4467
4611
  Utility function to send a Form to Shotgun and process any HTTP errors that
4468
4612
  could occur.
@@ -4502,42 +4646,7 @@ class Shotgun(object):
4502
4646
 
4503
4647
  return result
4504
4648
  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
- )
4649
+ raise ShotgunError("Max attempts limit reached.")
4541
4650
 
4542
4651
 
4543
4652
  # Helpers from the previous API, left as is.
@@ -4629,7 +4738,7 @@ class FormPostHandler(urllib.request.BaseHandler):
4629
4738
  return self.http_request(request)
4630
4739
 
4631
4740
 
4632
- def _translate_filters(filters, filter_operator):
4741
+ def _translate_filters(filters: Union[List, Tuple], filter_operator) -> Dict[str, Any]:
4633
4742
  """
4634
4743
  Translate filters params into data structure expected by rpc call.
4635
4744
  """
@@ -4638,7 +4747,7 @@ def _translate_filters(filters, filter_operator):
4638
4747
  return _translate_filters_dict(wrapped_filters)
4639
4748
 
4640
4749
 
4641
- def _translate_filters_dict(sg_filter):
4750
+ def _translate_filters_dict(sg_filter: Dict[str, Any]) -> Dict[str, Any]:
4642
4751
  new_filters = {}
4643
4752
  filter_operator = sg_filter.get("filter_operator")
4644
4753
 
@@ -4691,31 +4800,44 @@ def _translate_filters_simple(sg_filter):
4691
4800
  and condition["relation"] in ["is", "is_not", "in", "not_in"]
4692
4801
  and isinstance(values[0], dict)
4693
4802
  ):
4694
- values = [_get_type_and_id_from_value(v) for v in values]
4803
+ values = [_optimize_filter_field(v) for v in values]
4695
4804
 
4696
4805
  condition["values"] = values
4697
4806
 
4698
4807
  return condition
4699
4808
 
4700
4809
 
4701
- def _version_str(version):
4810
+ def _version_str(version) -> str:
4702
4811
  """
4703
4812
  Convert a tuple of int's to a '.' separated str.
4704
4813
  """
4705
4814
  return ".".join(map(str, version))
4706
4815
 
4707
4816
 
4708
- def _get_type_and_id_from_value(value):
4817
+ def _optimize_filter_field(
4818
+ field_value: Union[Dict[str, Any], List], recursive: bool = True
4819
+ ) -> Union[Dict, List]:
4709
4820
  """
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.
4821
+ For an FPT entity, returns a new dictionary with only the type,
4822
+ id, and other allowed keys.
4823
+ If case of any processing error, the original dictionary is returned.
4824
+
4825
+ At least `type` and `id` keys are required to do the optimization
4712
4826
  """
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
4827
+ allowed_keys = {
4828
+ "id",
4829
+ "type",
4830
+ "url",
4831
+ "name",
4832
+ "content_type",
4833
+ "local_path",
4834
+ "storage",
4835
+ "relative_path",
4836
+ }
4837
+ if isinstance(field_value, dict) and "id" in field_value and "type" in field_value:
4838
+ return {key: field_value[key] for key in allowed_keys if key in field_value}
4839
+
4840
+ elif recursive and isinstance(field_value, list):
4841
+ return [_optimize_filter_field(fv, recursive=False) for fv in field_value]
4842
+
4843
+ return field_value