sg-python-api 0.1.2__py3-none-any.whl → 0.2.0__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.
Files changed (56) hide show
  1. {sg_python_api-0.1.2.dist-info → sg_python_api-0.2.0.dist-info}/METADATA +21 -24
  2. sg_python_api-0.2.0.dist-info/RECORD +58 -0
  3. shotgun_api3/__init__.py +4 -0
  4. shotgun_api3/_async_client.py +347 -281
  5. shotgun_api3/_async_driver.py +25 -55
  6. shotgun_api3/_async_httpx2.py +92 -59
  7. shotgun_api3/_async_transport.py +38 -3
  8. shotgun_api3/_capabilities.py +4 -3
  9. shotgun_api3/_client.py +296 -422
  10. shotgun_api3/_config.py +245 -120
  11. shotgun_api3/_connections.py +144 -0
  12. shotgun_api3/_crud.py +25 -250
  13. shotgun_api3/_driver.py +29 -54
  14. shotgun_api3/_execution.py +1 -3
  15. shotgun_api3/_files.py +193 -21
  16. shotgun_api3/_methods.py +59 -411
  17. shotgun_api3/_protocol.py +27 -53
  18. shotgun_api3/_transport.py +82 -407
  19. shotgun_api3/_version.py +7 -2
  20. shotgun_api3/lib/mockgun/__init__.py +20 -7
  21. shotgun_api3/lib/mockgun/_activity.py +510 -0
  22. shotgun_api3/lib/mockgun/_async_client.py +196 -0
  23. shotgun_api3/lib/mockgun/_dates.py +164 -0
  24. shotgun_api3/lib/mockgun/_errors.py +18 -0
  25. shotgun_api3/lib/mockgun/_events.py +1031 -0
  26. shotgun_api3/lib/mockgun/_follows.py +217 -0
  27. shotgun_api3/lib/mockgun/_handlers.py +558 -0
  28. shotgun_api3/lib/mockgun/_links.py +440 -0
  29. shotgun_api3/lib/mockgun/_new_client.py +92 -0
  30. shotgun_api3/lib/mockgun/_presets.py +492 -0
  31. shotgun_api3/lib/mockgun/_query.py +2163 -0
  32. shotgun_api3/lib/mockgun/_retirement.py +368 -0
  33. shotgun_api3/lib/mockgun/_schema.py +172 -0
  34. shotgun_api3/lib/mockgun/_schema_writes.py +425 -0
  35. shotgun_api3/lib/mockgun/_search.py +378 -0
  36. shotgun_api3/lib/mockgun/_server.py +169 -0
  37. shotgun_api3/lib/mockgun/_sessions.py +206 -0
  38. shotgun_api3/lib/mockgun/_site.py +150 -0
  39. shotgun_api3/lib/mockgun/_site_data.py +584 -0
  40. shotgun_api3/lib/mockgun/_storage.py +208 -0
  41. shotgun_api3/lib/mockgun/_store.py +1446 -0
  42. shotgun_api3/lib/mockgun/_summaries.py +964 -0
  43. shotgun_api3/lib/mockgun/_sync_client.py +190 -0
  44. shotgun_api3/lib/mockgun/_threads.py +226 -0
  45. shotgun_api3/lib/mockgun/_thumbnails.py +146 -0
  46. shotgun_api3/lib/mockgun/_transport.py +145 -0
  47. shotgun_api3/lib/mockgun/_uploads.py +417 -0
  48. shotgun_api3/lib/mockgun/_wire.py +120 -0
  49. shotgun_api3/lib/mockgun/mockgun.py +14 -0
  50. shotgun_api3/lib/sgtimezone.py +24 -1
  51. sg_python_api-0.1.2.dist-info/RECORD +0 -31
  52. shotgun_api3/_signatures.py +0 -53
  53. shotgun_api3/lib/mockgun/_async.py +0 -315
  54. shotgun_api3/lib/mockgun/_mockgun.py +0 -1666
  55. {sg_python_api-0.1.2.dist-info → sg_python_api-0.2.0.dist-info}/WHEEL +0 -0
  56. {sg_python_api-0.1.2.dist-info → sg_python_api-0.2.0.dist-info}/licenses/LICENSE +0 -0
shotgun_api3/_client.py CHANGED
@@ -3,16 +3,16 @@
3
3
  import copy
4
4
  import dataclasses
5
5
  import datetime as _dt_module
6
- import inspect
7
6
  import os
8
7
  import threading
9
8
  import urllib.request
10
- import uuid
11
9
  from collections.abc import Callable, Generator, Iterator, Mapping
12
10
  from contextlib import ExitStack, contextmanager, suppress
11
+ from functools import partial
13
12
  from pathlib import Path
14
13
  from typing import (
15
14
  Any,
15
+ ClassVar,
16
16
  Literal,
17
17
  Never,
18
18
  TypeAlias,
@@ -48,7 +48,6 @@ from ._execution import (
48
48
  release_lease,
49
49
  retire_transport,
50
50
  )
51
- from ._signatures import bind_arguments, expose_signature
52
51
  from ._transport import (
53
52
  SendError,
54
53
  StreamingResponse,
@@ -58,152 +57,12 @@ from ._transport import (
58
57
  TransportResponse,
59
58
  Urllib3Transport,
60
59
  )
61
- from ._version import PACKAGE_VERSION
60
+ from ._version import COMPATIBILITY_VERSION
62
61
 
63
62
  _ReadinessKey: TypeAlias = tuple[str, int]
64
63
  _UploadResult = TypeVar('_UploadResult')
65
64
 
66
- __version__ = PACKAGE_VERSION
67
-
68
- _CONSTRUCTOR_PARAMETERS = (
69
- inspect.Parameter('base_url', inspect.Parameter.POSITIONAL_OR_KEYWORD, annotation=str),
70
- inspect.Parameter(
71
- 'script_name',
72
- inspect.Parameter.POSITIONAL_OR_KEYWORD,
73
- default=None,
74
- annotation=str | None,
75
- ),
76
- inspect.Parameter(
77
- 'api_key',
78
- inspect.Parameter.POSITIONAL_OR_KEYWORD,
79
- default=None,
80
- annotation=str | None,
81
- ),
82
- inspect.Parameter(
83
- 'convert_datetimes_to_utc',
84
- inspect.Parameter.POSITIONAL_OR_KEYWORD,
85
- default=True,
86
- annotation=bool,
87
- ),
88
- inspect.Parameter(
89
- 'http_proxy',
90
- inspect.Parameter.POSITIONAL_OR_KEYWORD,
91
- default=None,
92
- annotation=str | None,
93
- ),
94
- inspect.Parameter(
95
- 'connect',
96
- inspect.Parameter.POSITIONAL_OR_KEYWORD,
97
- default=True,
98
- annotation=bool,
99
- ),
100
- inspect.Parameter(
101
- 'ca_certs',
102
- inspect.Parameter.POSITIONAL_OR_KEYWORD,
103
- default=None,
104
- annotation=str | None,
105
- ),
106
- inspect.Parameter(
107
- 'login',
108
- inspect.Parameter.POSITIONAL_OR_KEYWORD,
109
- default=None,
110
- annotation=str | None,
111
- ),
112
- inspect.Parameter(
113
- 'password',
114
- inspect.Parameter.POSITIONAL_OR_KEYWORD,
115
- default=None,
116
- annotation=str | None,
117
- ),
118
- inspect.Parameter(
119
- 'sudo_as_login',
120
- inspect.Parameter.POSITIONAL_OR_KEYWORD,
121
- default=None,
122
- annotation=str | None,
123
- ),
124
- inspect.Parameter(
125
- 'session_token',
126
- inspect.Parameter.POSITIONAL_OR_KEYWORD,
127
- default=None,
128
- annotation=str | None,
129
- ),
130
- inspect.Parameter(
131
- 'auth_token',
132
- inspect.Parameter.POSITIONAL_OR_KEYWORD,
133
- default=None,
134
- annotation=str | None,
135
- ),
136
- )
137
- _UPLOAD_DOCSTRING = """Upload a file or thumbnail and return its Attachment ID.
138
-
139
- A server requiring legacy form uploads raises `ShotgunError` before
140
- any upload request is sent. Server capability reads may occur.
141
-
142
- Args:
143
- entity_type: Entity type receiving the upload.
144
- entity_id: Numeric identifier of the receiving entity.
145
- path: Existing non-empty file path as text or filesystem-encoded bytes.
146
- field_name: Optional file field receiving an Attachment.
147
- display_name: Optional Attachment display name.
148
- tag_list: Optional comma-delimited Attachment tags.
149
- created_at: Optional Attachment creation timestamp.
150
-
151
- Returns:
152
- Created Attachment ID.
153
-
154
- Raises:
155
- TypeError: If arguments do not match the documented signature.
156
- ShotgunError: If the file or an upload response is invalid, an RPC
157
- fault or terminal transport failure occurs, or the server
158
- requires legacy form uploads.
159
- ProtocolError: If an upload endpoint returns an HTTP error.
160
- ValueError: If authenticated HTTP violates the configured origin policy.
161
- """
162
- _UPLOAD_PARAMETERS = (
163
- inspect.Parameter('entity_type', inspect.Parameter.POSITIONAL_OR_KEYWORD, annotation=str),
164
- inspect.Parameter('entity_id', inspect.Parameter.POSITIONAL_OR_KEYWORD, annotation=int),
165
- inspect.Parameter('path', inspect.Parameter.POSITIONAL_OR_KEYWORD, annotation=str | bytes),
166
- inspect.Parameter(
167
- 'field_name',
168
- inspect.Parameter.POSITIONAL_OR_KEYWORD,
169
- default=None,
170
- annotation=str | None,
171
- ),
172
- inspect.Parameter(
173
- 'display_name',
174
- inspect.Parameter.POSITIONAL_OR_KEYWORD,
175
- default=None,
176
- annotation=str | None,
177
- ),
178
- inspect.Parameter(
179
- 'tag_list',
180
- inspect.Parameter.POSITIONAL_OR_KEYWORD,
181
- default=None,
182
- annotation=str | None,
183
- ),
184
- inspect.Parameter(
185
- 'created_at',
186
- inspect.Parameter.POSITIONAL_OR_KEYWORD,
187
- default=None,
188
- annotation=_dt_module.datetime | None,
189
- ),
190
- )
191
- CONSTRUCTOR_SIGNATURE = inspect.Signature(_CONSTRUCTOR_PARAMETERS, return_annotation=None)
192
- _METHOD_CONSTRUCTOR_SIGNATURE = inspect.Signature(
193
- (
194
- inspect.Parameter('self', inspect.Parameter.POSITIONAL_OR_KEYWORD),
195
- *_CONSTRUCTOR_PARAMETERS,
196
- ),
197
- return_annotation=None,
198
- )
199
- _METHOD_UPLOAD_SIGNATURE = inspect.Signature(
200
- (
201
- inspect.Parameter('self', inspect.Parameter.POSITIONAL_OR_KEYWORD),
202
- *_UPLOAD_PARAMETERS,
203
- ),
204
- return_annotation=int,
205
- )
206
- _UPLOAD_SIGNATURE = inspect.Signature(_UPLOAD_PARAMETERS, return_annotation=int)
65
+ __version__ = COMPATIBILITY_VERSION
207
66
 
208
67
 
209
68
  @dataclasses.dataclass(frozen=True)
@@ -272,13 +131,16 @@ class _DownloadExchange:
272
131
  transfer = ExceptionCapture()
273
132
  try:
274
133
  with transfer:
275
- handle = stack.enter_context(_files.download_temporary(destination))
134
+ with _files.download_destination_io(destination):
135
+ handle = stack.enter_context(_files.download_temporary(destination))
276
136
  temporary = Path(handle.name)
277
137
  control.consume_into(
278
138
  chunks,
279
- handle.write,
139
+ partial(_files.write_download_chunk, destination, handle),
280
140
  total_bytes=total_bytes,
281
141
  )
142
+ with _files.download_destination_io(destination):
143
+ handle.flush()
282
144
  opened_stack = stack
283
145
  stack = None
284
146
  opened_cleanup = SuppressedExceptionCapture()
@@ -288,7 +150,8 @@ class _DownloadExchange:
288
150
  raise _DownloadCleanupError from None
289
151
  if opened_cleanup.exception is not None:
290
152
  raise opened_cleanup.exception
291
- temporary.replace(destination)
153
+ with _files.download_destination_io(destination):
154
+ temporary.replace(destination)
292
155
  finally:
293
156
  close_cleanup = SuppressedExceptionCapture()
294
157
  unlink_cleanup = SuppressedExceptionCapture()
@@ -317,12 +180,23 @@ class _DownloadExchange:
317
180
  return control.consume(chunks, total_bytes=total_bytes)
318
181
  return self._write_file(chunks, control, total_bytes)
319
182
 
320
- def _execute(
183
+ def execute(
321
184
  self,
322
185
  driver: Driver,
323
186
  operation: _protocol.OperationDescriptor,
324
187
  ) -> bytes | str:
325
- """Execute the workflow through the common streaming attempt loop."""
188
+ """Execute the workflow through the common streaming attempt loop.
189
+
190
+ Returns:
191
+ Downloaded bytes, or the destination path for a file download.
192
+
193
+ Raises:
194
+ SendError: If required download cleanup fails.
195
+ TransportError: If the attempt loop cannot complete the transfer.
196
+ ShotgunFileDownloadError: If the response or redirect is invalid,
197
+ or creating, writing, flushing, or publishing the destination
198
+ file fails.
199
+ """
326
200
  try:
327
201
  return driver.execute(
328
202
  operation,
@@ -349,16 +223,6 @@ class _InvocationOptions:
349
223
  auth_spec: _protocol.AuthSpec | None = None
350
224
 
351
225
 
352
- def _bind_upload(
353
- args: tuple[object, ...],
354
- kwargs: dict[str, object],
355
- ) -> _files.UploadArguments:
356
- """Bind runtime arguments with upload-specific errors."""
357
- bound = bind_arguments(_UPLOAD_SIGNATURE, args, kwargs, 'Shotgun.upload()')
358
- arguments = cast('Callable[..., _files.UploadArguments]', _files.UploadArguments)
359
- return arguments(**bound.arguments)
360
-
361
-
362
226
  def _execute_upload_exchange(
363
227
  driver: Driver,
364
228
  context: RequestContext,
@@ -374,7 +238,6 @@ def _execute_upload_exchange(
374
238
  headers=dict(exchange.headers),
375
239
  body=exchange.body_factory(),
376
240
  timeout=context.timeout,
377
- token=uuid.uuid4().hex,
378
241
  )
379
242
 
380
243
  def consume(
@@ -400,43 +263,23 @@ def _execute_upload_exchange(
400
263
  )
401
264
 
402
265
 
403
- def _expose_upload_signature(function: Callable[..., object]) -> Callable[..., object]:
404
- """Expose the observed upload signature on its variadic implementation."""
405
- return expose_signature(function, _METHOD_UPLOAD_SIGNATURE, _UPLOAD_DOCSTRING)
406
-
407
-
408
- def bind_constructor(
409
- args: tuple[object, ...],
410
- kwargs: dict[str, object],
411
- client_name: str,
412
- ) -> ConstructorOptions:
413
- """Bind constructor arguments with client-specific errors.
266
+ def build_config(options: ConstructorOptions, *, in_process: bool = False) -> Config:
267
+ """Initialize and validate network-free configuration, auth, and proxy fields.
414
268
 
415
269
  Args:
416
- args: Positional public arguments.
417
- kwargs: Keyword public arguments.
418
- client_name: Public client class name for error attribution.
270
+ options: Constructor values for the site, authentication, and proxy.
271
+ in_process: Whether requests stay inside this process, allowing
272
+ authentication without the network HTTP origin restriction.
419
273
 
420
274
  Returns:
421
- Bound immutable constructor options.
422
-
423
- Raises:
424
- TypeError: If the arguments do not match the public signature.
425
- """
426
- bound = bind_arguments(CONSTRUCTOR_SIGNATURE, args, kwargs, f'{client_name}.__init__()')
427
- constructor = cast('Callable[..., ConstructorOptions]', ConstructorOptions)
428
- return constructor(**bound.arguments)
429
-
430
-
431
- def build_config(options: ConstructorOptions) -> Config:
432
- """Initialize and validate network-free configuration, auth, and proxy fields.
275
+ A new configuration containing the validated constructor settings.
433
276
 
434
277
  Raises:
435
278
  TypeError: If a configuration or proxy value has an unsupported type.
436
279
  ValueError: If authentication, site or proxy values, or
437
280
  `SHOTGUN_API_RETRY_INTERVAL` are invalid.
438
281
  """
439
- config = Config()
282
+ config = Config(_in_process=in_process)
440
283
  config.set_server_params(options.base_url)
441
284
  config.update_fields(
442
285
  api_key=options.api_key,
@@ -455,26 +298,6 @@ def build_config(options: ConstructorOptions) -> Config:
455
298
  return config
456
299
 
457
300
 
458
- def expose_constructor_signature(function: Callable[..., object]) -> Callable[..., object]:
459
- """Expose the documented constructor signature.
460
-
461
- Args:
462
- function: Variadic constructor implementation.
463
-
464
- Returns:
465
- The same function with its public runtime signature attached.
466
- """
467
- expose_signature(function, _METHOD_CONSTRUCTOR_SIGNATURE, None)
468
- annotations = {
469
- name: parameter.annotation
470
- for name, parameter in CONSTRUCTOR_SIGNATURE.parameters.items()
471
- if parameter.annotation is not inspect.Parameter.empty
472
- }
473
- annotations['return'] = CONSTRUCTOR_SIGNATURE.return_annotation
474
- object.__setattr__(function, '__annotations__', annotations)
475
- return function
476
-
477
-
478
301
  class Shotgun:
479
302
  """Provide the synchronous Flow Production Tracking API client surface.
480
303
 
@@ -488,14 +311,12 @@ class Shotgun:
488
311
  config: The writable client configuration.
489
312
  server_caps: Validated capabilities for the configured origin.
490
313
  server_info: Raw validated server metadata.
314
+ transport: Current transport backend, or `None` before connection or
315
+ after retirement.
491
316
  """
492
317
 
493
- __signature__ = CONSTRUCTOR_SIGNATURE
494
-
495
- @overload
496
- def __init__(self, base_url: Never) -> None: ...
318
+ _in_process: ClassVar[bool] = False
497
319
 
498
- @overload
499
320
  def __init__(
500
321
  self,
501
322
  base_url: str,
@@ -510,10 +331,7 @@ class Shotgun:
510
331
  sudo_as_login: str | None = None,
511
332
  session_token: str | None = None,
512
333
  auth_token: str | None = None,
513
- ) -> None: ...
514
-
515
- @expose_constructor_signature
516
- def __init__(self, *args: object, **kwargs: object) -> None:
334
+ ) -> None:
517
335
  """Initialize a client for one site and authentication mode.
518
336
 
519
337
  Supply exactly one authentication mode: `script_name` with `api_key`,
@@ -525,30 +343,60 @@ class Shotgun:
525
343
  `http_proxy` accepts `[user:pass@]host[:port]`, with port 8080 when
526
344
  omitted. A truthy `ca_certs` selects a CA bundle; otherwise
527
345
  `SHOTGUN_API_CACERTS` is consulted before using system trust roots.
346
+ `SHOTGUN_API_RETRY_INTERVAL` supplies a nonnegative integer retry
347
+ interval in milliseconds, defaulting to 3000.
528
348
 
529
349
  Construction performs server-readiness I/O by default. Supplying
530
350
  `auth_token` also performs a session-token exchange during
531
351
  construction, even when `connect` is `False`.
532
352
 
533
353
  Args:
534
- *args: Positional values from the documented constructor signature.
535
- **kwargs: Keyword values from the documented constructor signature.
354
+ base_url: Flow Production Tracking site URL.
355
+ script_name: Script name for script authentication.
356
+ api_key: API key for script authentication.
357
+ convert_datetimes_to_utc: Whether to convert datetime values to
358
+ UTC.
359
+ http_proxy: Optional proxy authority in `[user:pass@]host[:port]`
360
+ form.
361
+ connect: Whether to perform eager readiness during construction.
362
+ ca_certs: Optional certificate-authority bundle path.
363
+ login: Login for human-user authentication.
364
+ password: Password for human-user authentication.
365
+ sudo_as_login: Optional user login for sudo impersonation.
366
+ session_token: Existing session token for authentication.
367
+ auth_token: Optional two-factor authentication token.
536
368
 
537
369
  Raises:
538
- TypeError: If arguments do not match the documented signature.
539
- ValueError: If authentication, URL, or proxy values are invalid.
370
+ ValueError: If authentication, URL, or proxy values are invalid,
371
+ the configured CA bundle is not a readable file, or
372
+ `SHOTGUN_API_RETRY_INTERVAL` is not a nonnegative integer.
540
373
  ShotgunError: If authentication, server metadata, or an RPC result
541
374
  is invalid, or a transport attempt fails.
542
375
  ProtocolError: If eager readiness or token exchange receives an
543
376
  HTTP error.
544
377
  """
545
- self._initialize(bind_constructor(args, kwargs, 'Shotgun'))
378
+ self._initialize(
379
+ ConstructorOptions(
380
+ base_url,
381
+ script_name,
382
+ api_key,
383
+ convert_datetimes_to_utc,
384
+ http_proxy,
385
+ connect,
386
+ ca_certs,
387
+ login,
388
+ password,
389
+ sudo_as_login,
390
+ session_token,
391
+ auth_token,
392
+ )
393
+ )
546
394
 
547
395
  @property
548
396
  def _connection(self) -> Transport | None:
549
397
  """Return the current private connection compatibility view."""
550
398
  with self._transport_lock:
551
- return self._transport
399
+ return self.transport
552
400
 
553
401
  def _invoke(
554
402
  self,
@@ -571,12 +419,16 @@ class Shotgun:
571
419
  return _protocol.transform_inbound(result, context.convert_datetimes_to_utc)
572
420
 
573
421
  def _new_transport(self, context: RequestContext) -> Transport:
574
- """Create a transport for one captured generation."""
422
+ """Create a transport for one generation; mockgun overrides this package-internal hook."""
423
+ if context.in_process:
424
+ raise RuntimeError('an in-process client must supply its own transport')
575
425
  return Urllib3Transport(
576
426
  ca_certs=self._ca_certs,
577
427
  proxy_headers=dict(context.proxy_headers) or None,
578
428
  proxy_url=context.proxy_url,
579
429
  pool_maxsize=context.pool_maxsize,
430
+ max_connection_idle_secs=context.max_connection_idle_secs,
431
+ tcp_keepalive_timeout_secs=context.tcp_keepalive_timeout_secs,
580
432
  )
581
433
 
582
434
  def _publish_capabilities(
@@ -614,34 +466,39 @@ class Shotgun:
614
466
  """Lease the driver and transport for one complete logical operation."""
615
467
  close_after_unlock: list[Transport] = []
616
468
  with self._transport_lock:
617
- if self._known_transport is not self._transport:
469
+ decision = _files.choose_driver_lease(
470
+ self._known_transport,
471
+ self.transport,
472
+ self._driver_generation,
473
+ context.transport_generation,
474
+ self._transport_owned,
475
+ )
476
+ if decision.known_changed:
618
477
  if self._known_transport is not None:
619
478
  retired = retire_transport(
620
479
  self._lease_counts, self._retired_transports, self._known_transport
621
480
  )
622
481
  if retired is not None:
623
482
  close_after_unlock.append(retired)
624
- self._known_transport = self._transport
483
+ self._known_transport = self.transport
625
484
  self._driver = None
626
485
  self._driver_generation = None
627
486
  self._transport_owned = False
628
487
 
629
- transport = self._transport
488
+ transport = self.transport
630
489
  if transport is None:
631
490
  transport = self._new_transport(context)
632
- self._transport = transport
491
+ self.transport = transport
633
492
  self._known_transport = transport
634
493
  self._transport_owned = True
635
- elif self._driver_generation not in {None, context.transport_generation}:
636
- if self._transport_owned:
637
- retired = retire_transport(
638
- self._lease_counts, self._retired_transports, transport
639
- )
640
- if retired is not None:
641
- close_after_unlock.append(retired)
642
- transport = self._new_transport(context)
643
- self._transport = transport
644
- self._known_transport = transport
494
+ elif decision.replace_owned_transport:
495
+ retired = retire_transport(self._lease_counts, self._retired_transports, transport)
496
+ if retired is not None:
497
+ close_after_unlock.append(retired)
498
+ transport = self._new_transport(context)
499
+ self.transport = transport
500
+ self._known_transport = transport
501
+ if decision.reset_driver:
645
502
  self._driver = None
646
503
 
647
504
  if self._driver is None:
@@ -799,7 +656,9 @@ class Shotgun:
799
656
  _files.SHARE_THUMBNAIL,
800
657
  fields,
801
658
  ),
802
- lambda text, _headers: _files.parse_link_completion(text),
659
+ lambda text, _headers: _files.parse_link_completion(
660
+ text, operation='share_thumbnail', entities=entities
661
+ ),
803
662
  )
804
663
 
805
664
  def _thumbnail_source_ready(
@@ -873,7 +732,7 @@ class Shotgun:
873
732
  _files.StorageRange(
874
733
  length=prepared.file_size,
875
734
  offset=0,
876
- path=prepared.options.arguments.path,
735
+ path=prepared.path,
877
736
  ),
878
737
  ),
879
738
  lambda _text, _headers: None,
@@ -904,7 +763,16 @@ class Shotgun:
904
763
  _files.LINK_FILE,
905
764
  fields,
906
765
  ),
907
- lambda text, _headers: _files.parse_link_completion(text),
766
+ lambda text, _headers: _files.parse_link_completion(
767
+ text,
768
+ operation='upload link',
769
+ entities=[
770
+ {
771
+ 'type': prepared.options.arguments.entity_type,
772
+ 'id': prepared.options.arguments.entity_id,
773
+ }
774
+ ],
775
+ ),
908
776
  )
909
777
 
910
778
  @staticmethod
@@ -973,16 +841,8 @@ class Shotgun:
973
841
  prepared: _files.PreparedUpload,
974
842
  ) -> int:
975
843
  """Execute one prepared upload through a captured driver lease."""
976
- arguments = prepared.options.arguments
977
844
  capabilities = self._ensure_readiness(context, driver)
978
- upload_path = _files.require_supported_upload_path(
979
- _files.choose_upload_path(
980
- capabilities.server_info,
981
- arguments.entity_type,
982
- arguments.field_name,
983
- prepared.file_size,
984
- )
985
- )
845
+ upload_path = _files.choose_prepared_upload_path(capabilities.server_info, prepared)
986
846
  multipart = upload_path == 'multipart'
987
847
  upload_info = self._upload_allocation(driver, context, prepared, multipart)
988
848
  if multipart:
@@ -1007,14 +867,8 @@ class Shotgun:
1007
867
  ) -> int:
1008
868
  """Execute the exact direct-storage thumbnail workflow."""
1009
869
  capabilities = self._ensure_readiness(context, driver)
1010
- arguments = prepared.options.arguments
1011
- _files.require_supported_upload_path(
1012
- _files.choose_upload_path(
1013
- capabilities.server_info,
1014
- arguments.entity_type,
1015
- arguments.field_name,
1016
- prepared.file_size,
1017
- )
870
+ _files.choose_prepared_upload_path(
871
+ capabilities.server_info, prepared, sharing_thumbnail=True
1018
872
  )
1019
873
  upload_info = self._upload_allocation(driver, context, prepared, multipart=False)
1020
874
  self._upload_direct(driver, context, prepared, upload_info)
@@ -1042,10 +896,6 @@ class Shotgun:
1042
896
  result.update({field_name: current.get(field_name) for field_name in thumbnails})
1043
897
  return result
1044
898
 
1045
- @overload
1046
- def activity_stream_read(self, entity_type: Never) -> dict[str, object]: ...
1047
-
1048
- @overload
1049
899
  def activity_stream_read(
1050
900
  self,
1051
901
  entity_type: str,
@@ -1054,30 +904,30 @@ class Shotgun:
1054
904
  min_id: int | None = None,
1055
905
  max_id: int | None = None,
1056
906
  limit: int | None = None,
1057
- ) -> dict[str, object]: ...
1058
-
1059
- @_methods.expose_activity_stream_read_signature
1060
- def activity_stream_read(self, *args: object, **kwargs: object) -> dict[str, object]:
907
+ ) -> dict[str, object]:
1061
908
  """Read the activity stream for one entity.
1062
909
 
1063
910
  Args:
1064
- *args: Positional arguments from the documented activity-stream
1065
- signature.
1066
- **kwargs: Keyword arguments from the documented activity-stream
1067
- signature.
911
+ entity_type: Entity type whose activity stream to read.
912
+ entity_id: Numeric identifier of the entity.
913
+ entity_fields: Optional fields to return for linked entity types.
914
+ min_id: Optional minimum activity identifier.
915
+ max_id: Optional maximum activity identifier.
916
+ limit: Optional maximum number of activity records.
1068
917
 
1069
918
  Returns:
1070
919
  Activity stream data returned by the server.
1071
920
 
1072
921
  Raises:
1073
- TypeError: If arguments do not match the documented signature.
1074
922
  ShotgunError: If an RPC fault or terminal transport failure occurs,
1075
923
  or if the server result has the wrong type.
1076
924
  ProtocolError: If a server request returns an HTTP error.
1077
925
  ValueError: If authenticated HTTP violates the configured origin
1078
926
  policy.
1079
927
  """
1080
- options = _methods.bind_activity_stream_read(args, kwargs, 'Shotgun')
928
+ options = _methods.ActivityStreamReadOptions(
929
+ entity_type, entity_id, entity_fields, min_id, max_id, limit
930
+ )
1081
931
  return _crud.require_dictionary_result(
1082
932
  self._call(
1083
933
  'activity_stream',
@@ -1163,7 +1013,10 @@ class Shotgun:
1163
1013
  type, or if the server result is not a list.
1164
1014
  ProtocolError: If a server request returns an HTTP error.
1165
1015
  ValueError: If authenticated HTTP violates the configured origin
1166
- policy.
1016
+ policy, or a naive datetime cannot be converted to UTC.
1017
+ OverflowError: If converting an aware datetime to UTC exceeds the
1018
+ supported datetime range.
1019
+ OSError: If the platform cannot resolve a local datetime.
1167
1020
  """
1168
1021
  calls = _crud.build_batch_calls(requests)
1169
1022
  if not calls:
@@ -1236,21 +1089,29 @@ class Shotgun:
1236
1089
  )
1237
1090
 
1238
1091
  def close(self) -> None:
1239
- """Retire known and current transports after in-flight work releases them."""
1092
+ """Retire known and current transports after in-flight work releases them.
1093
+
1094
+ Attempt the remaining immediately eligible closes if one fails, then
1095
+ propagate the first failure.
1096
+
1097
+ Raises:
1098
+ Exception: If a transport eligible for immediate cleanup fails to
1099
+ close.
1100
+ """
1240
1101
  close_after_unlock: list[Transport] = []
1241
1102
  with self._transport_lock:
1242
1103
  advance_transport_generation(self._config)
1243
- if self._known_transport is not None and self._known_transport is not self._transport:
1104
+ if self._known_transport is not None and self._known_transport is not self.transport:
1244
1105
  retired = retire_transport(
1245
1106
  self._lease_counts, self._retired_transports, self._known_transport
1246
1107
  )
1247
1108
  if retired is not None:
1248
1109
  close_after_unlock.append(retired)
1249
- transport = self._transport
1110
+ transport = self.transport
1250
1111
  self._driver = None
1251
1112
  self._driver_generation = None
1252
1113
  self._known_transport = None
1253
- self._transport = None
1114
+ self.transport = None
1254
1115
  self._transport_owned = False
1255
1116
  if transport is not None:
1256
1117
  retired = retire_transport(self._lease_counts, self._retired_transports, transport)
@@ -1321,7 +1182,10 @@ class Shotgun:
1321
1182
  file. A thumbnail failure occurs after the entity is created.
1322
1183
  ProtocolError: If a server request returns an HTTP error.
1323
1184
  ValueError: If authenticated HTTP violates the configured origin
1324
- policy.
1185
+ policy, or a naive datetime cannot be converted to UTC.
1186
+ OverflowError: If converting an aware datetime to UTC exceeds the
1187
+ supported datetime range.
1188
+ OSError: If the platform cannot resolve a local datetime.
1325
1189
  """
1326
1190
  field_data, thumbnails = _files.split_thumbnail_uploads(data, keep_image_none=False)
1327
1191
  params = _crud.build_create_params(entity_type, field_data, return_fields)
@@ -1389,13 +1253,13 @@ class Shotgun:
1389
1253
 
1390
1254
  Raises:
1391
1255
  TypeError: If no attachment is given or the reference is a string.
1392
- ValueError: If an entity dictionary references a different type.
1256
+ ValueError: If an entity dictionary references a different type,
1257
+ server settings are incomplete, or authenticated HTTP violates
1258
+ the configured origin security policy.
1393
1259
  ShotgunError: If another attachment reference is unsupported or a
1394
1260
  setup RPC reports a fault; the fault keeps its specific type.
1395
1261
  ShotgunFileDownloadError: If the resolved URL is invalid or setup,
1396
1262
  transfer, or destination I/O fails.
1397
- ValueError: If server settings are incomplete or authenticated HTTP
1398
- violates the configured origin security policy.
1399
1263
  """
1400
1264
  if attachment is False:
1401
1265
  if attachment_id is None:
@@ -1430,7 +1294,7 @@ class Shotgun:
1430
1294
  headers=headers,
1431
1295
  url=url,
1432
1296
  )
1433
- return exchange._execute(driver, operation) # noqa: SLF001 # pyright: ignore[reportPrivateUsage]
1297
+ return exchange.execute(driver, operation)
1434
1298
  except _errors.ShotgunFileDownloadError:
1435
1299
  raise
1436
1300
  except (TransportError, _errors.ProtocolError, OSError):
@@ -1445,54 +1309,36 @@ class Shotgun:
1445
1309
  """
1446
1310
  raise _methods.entity_types_alias_error()
1447
1311
 
1448
- @overload
1449
1312
  def export_page(
1450
1313
  self,
1451
1314
  page_id: int,
1452
- page_format: str,
1453
- /,
1315
+ format: str, # noqa: A002
1454
1316
  layout_name: str | None = None,
1455
- ) -> str: ...
1456
-
1457
- @overload
1458
- def export_page(
1459
- self,
1460
- page_id: int,
1461
- **kwargs: Unpack[_methods.ExportPageKeywordArguments],
1462
- ) -> str: ...
1463
-
1464
- @_methods.expose_export_page_signature
1465
- def export_page(self, *args: object, **kwargs: object) -> str:
1317
+ ) -> str:
1466
1318
  """Export a page using the documented arguments.
1467
1319
 
1468
1320
  The Page or its layout must be marked API Exportable in the web UI, or
1469
1321
  the server rejects the export.
1470
1322
 
1471
1323
  Args:
1472
- *args: Positional values from the documented `export_page`
1473
- signature.
1474
- **kwargs: Keyword values from the documented `export_page`
1475
- signature.
1324
+ page_id: Numeric page identifier.
1325
+ format: Required nonempty export format name.
1326
+ layout_name: Optional layout name, including an empty name.
1476
1327
 
1477
1328
  Returns:
1478
1329
  Exported page text returned by the server.
1479
1330
 
1480
1331
  Raises:
1481
- TypeError: If arguments do not match the documented signature.
1482
1332
  ShotgunError: If an RPC fault or terminal transport failure occurs,
1483
- or if an argument or the server result has the wrong type.
1333
+ or if `page_id`, `format`, or the server result is invalid.
1484
1334
  ProtocolError: If a server request returns an HTTP error.
1485
1335
  ValueError: If authenticated HTTP violates the configured origin
1486
1336
  policy.
1487
1337
  """
1488
- options = _methods.bind_export_page(args, kwargs, 'Shotgun')
1338
+ options = _methods.ExportPageOptions(page_id, format, layout_name)
1489
1339
  params = _methods.build_export_page_params(options)
1490
1340
  return _methods.require_string_result(self._call('export_page', params), 'export_page')
1491
1341
 
1492
- @overload
1493
- def find(self, entity_type: Never) -> list[dict[str, object]]: ...
1494
-
1495
- @overload
1496
1342
  def find(
1497
1343
  self,
1498
1344
  entity_type: str,
@@ -1505,33 +1351,50 @@ class Shotgun:
1505
1351
  page: int = 0,
1506
1352
  include_archived_projects: bool = True,
1507
1353
  additional_filter_presets: object = None,
1508
- ) -> list[dict[str, object]]: ...
1509
-
1510
- @_crud.expose_find_signature
1511
- def find(self, *args: object, **kwargs: object) -> list[dict[str, object]]:
1354
+ ) -> list[dict[str, object]]:
1512
1355
  """Find entities matching the supplied filters.
1513
1356
 
1514
1357
  Args:
1515
- *args: Positional values from the documented `find` signature.
1516
- **kwargs: Keyword values from the documented `find` signature.
1358
+ entity_type: Entity type to find.
1359
+ filters: Filters in list or dict syntax.
1360
+ fields: Optional fields to return; defaults to `id`.
1361
+ order: Optional field and direction sort specifications.
1362
+ filter_operator: `'all'` or `'any'` for list-syntax filters.
1363
+ limit: Maximum entities to return, or `0` for no limit.
1364
+ retired_only: Return retired entities instead of active entities.
1365
+ page: Specific page to return, or `0` to read all pages.
1366
+ include_archived_projects: Include entities from archived projects.
1367
+ additional_filter_presets: Optional server-side filter presets.
1517
1368
 
1518
1369
  Returns:
1519
1370
  Matching entities in server order.
1520
1371
 
1521
1372
  Raises:
1522
- TypeError: If arguments do not match the documented signature.
1523
1373
  ShotgunError: If an RPC fault or terminal transport failure occurs,
1524
- or if filters, paging, ordering, or a server result is invalid.
1374
+ or if filters, fields, paging, ordering, or a server result is
1375
+ invalid.
1525
1376
  ProtocolError: If a server request returns an HTTP error.
1526
1377
  ValueError: If authenticated HTTP violates the configured origin
1527
- policy.
1378
+ policy, or a naive datetime cannot be converted to UTC.
1379
+ OverflowError: If converting an aware datetime to UTC exceeds the
1380
+ supported datetime range.
1381
+ OSError: If the platform cannot resolve a local datetime.
1528
1382
  """
1529
- return self._find(_crud.bind_find(args, kwargs, 'Shotgun'))
1530
-
1531
- @overload
1532
- def find_one(self, entity_type: Never) -> dict[str, object] | None: ...
1383
+ return self._find(
1384
+ _crud.FindOptions(
1385
+ entity_type,
1386
+ filters,
1387
+ fields,
1388
+ order,
1389
+ filter_operator,
1390
+ limit,
1391
+ retired_only,
1392
+ page,
1393
+ include_archived_projects,
1394
+ additional_filter_presets,
1395
+ )
1396
+ )
1533
1397
 
1534
- @overload
1535
1398
  def find_one(
1536
1399
  self,
1537
1400
  entity_type: str,
@@ -1542,28 +1405,43 @@ class Shotgun:
1542
1405
  retired_only: bool = False,
1543
1406
  include_archived_projects: bool = True,
1544
1407
  additional_filter_presets: object = None,
1545
- ) -> dict[str, object] | None: ...
1546
-
1547
- @_crud.expose_find_one_signature
1548
- def find_one(self, *args: object, **kwargs: object) -> dict[str, object] | None:
1408
+ ) -> dict[str, object] | None:
1549
1409
  """Return the first entity matching the supplied filters.
1550
1410
 
1551
1411
  Args:
1552
- *args: Positional values from the documented `find_one` signature.
1553
- **kwargs: Keyword values from the documented `find_one` signature.
1412
+ entity_type: Entity type to find.
1413
+ filters: Filters in list or dict syntax.
1414
+ fields: Optional fields to return; defaults to `id`.
1415
+ order: Optional field and direction sort specifications.
1416
+ filter_operator: `'all'` or `'any'` for list-syntax filters.
1417
+ retired_only: Return a retired entity instead of an active entity.
1418
+ include_archived_projects: Include entities from archived projects.
1419
+ additional_filter_presets: Optional server-side filter presets.
1554
1420
 
1555
1421
  Returns:
1556
1422
  The first matching entity, or `None` when no entity matches.
1557
1423
 
1558
1424
  Raises:
1559
- TypeError: If arguments do not match the documented signature.
1560
1425
  ShotgunError: If an RPC fault or terminal transport failure occurs,
1561
- or if filters, paging, ordering, or a server result is invalid.
1426
+ or if filters, fields, paging, ordering, or a server result is
1427
+ invalid.
1562
1428
  ProtocolError: If a server request returns an HTTP error.
1563
1429
  ValueError: If authenticated HTTP violates the configured origin
1564
- policy.
1430
+ policy, or a naive datetime cannot be converted to UTC.
1431
+ OverflowError: If converting an aware datetime to UTC exceeds the
1432
+ supported datetime range.
1433
+ OSError: If the platform cannot resolve a local datetime.
1565
1434
  """
1566
- options = _crud.bind_find_one(args, kwargs, 'Shotgun')
1435
+ options = _crud.FindOneOptions(
1436
+ entity_type,
1437
+ filters,
1438
+ fields,
1439
+ order,
1440
+ filter_operator,
1441
+ retired_only,
1442
+ include_archived_projects,
1443
+ additional_filter_presets,
1444
+ )
1567
1445
  return _crud.first_result(self._find(_crud.find_options_from_one(options)))
1568
1446
 
1569
1447
  def follow(self, user: dict[str, Any], entity: dict[str, Any]) -> dict[str, object]:
@@ -1713,16 +1591,20 @@ class Shotgun:
1713
1591
  def _initialize(self, options: ConstructorOptions) -> None:
1714
1592
  """Initialize client state from validated constructor options."""
1715
1593
  self._base_url = options.base_url.rstrip('/')
1716
- self._ca_certs = options.ca_certs or os.environ.get('SHOTGUN_API_CACERTS') or None
1594
+ self._ca_certs = (
1595
+ options.ca_certs or os.environ.get('SHOTGUN_API_CACERTS') or None
1596
+ if self._in_process
1597
+ else config_module.constructor_ca_certs(options.ca_certs)
1598
+ )
1717
1599
  self._client_caps = ClientCapabilities()
1718
- self._config = build_config(options)
1600
+ self._config = build_config(options, in_process=self._in_process)
1719
1601
 
1720
1602
  self._driver: Driver | None = None
1721
1603
  self._driver_generation: int | None = None
1722
1604
  self._known_transport: Transport | None = None
1723
1605
  self._lease_counts: dict[int, int] = {}
1724
1606
  self._retired_transports: dict[int, Transport] = {}
1725
- self._transport: Transport | None = None
1607
+ self.transport: Transport | None = None
1726
1608
  self._transport_lock = threading.Lock()
1727
1609
  self._transport_owned = False
1728
1610
 
@@ -2000,7 +1882,10 @@ class Shotgun:
2000
1882
  or if the server result is invalid.
2001
1883
  ProtocolError: If a server request returns an HTTP error.
2002
1884
  ValueError: If authenticated HTTP violates the configured origin
2003
- policy.
1885
+ policy, or a naive datetime cannot be converted to UTC.
1886
+ OverflowError: If converting an aware datetime to UTC exceeds the
1887
+ supported datetime range.
1888
+ OSError: If the platform cannot resolve a local datetime.
2004
1889
  """
2005
1890
  result = self._call(
2006
1891
  'schema_field_create',
@@ -2096,7 +1981,10 @@ class Shotgun:
2096
1981
  or if the server result is invalid.
2097
1982
  ProtocolError: If a server request returns an HTTP error.
2098
1983
  ValueError: If authenticated HTTP violates the configured origin
2099
- policy.
1984
+ policy, or a naive datetime cannot be converted to UTC.
1985
+ OverflowError: If converting an aware datetime to UTC exceeds the
1986
+ supported datetime range.
1987
+ OSError: If the platform cannot resolve a local datetime.
2100
1988
  """
2101
1989
  return _crud.require_boolean_result(
2102
1990
  self._call(
@@ -2216,30 +2104,23 @@ class Shotgun:
2216
2104
  source_entity,
2217
2105
  filmstrip_thumbnail,
2218
2106
  )
2219
- if share.thumbnail_path is not None and 'field_name' in kwargs:
2220
- raise TypeError("Shotgun.upload() got multiple values for argument 'field_name'")
2221
2107
  prepared: _files.PreparedUpload | None = None
2222
2108
  if share.thumbnail_path is not None:
2223
- arguments = _bind_upload(
2224
- (
2225
- share.source_entity_type,
2226
- share.source_entity_id,
2227
- share.thumbnail_path,
2228
- ),
2229
- {
2230
- **kwargs,
2231
- 'field_name': (
2232
- 'filmstrip_thumb_image' if filmstrip_thumbnail else 'thumb_image'
2233
- ),
2234
- },
2235
- )
2236
- prepared = _files.prepare_upload(
2237
- _files.UploadOptions(
2238
- arguments=arguments,
2239
- upload_type='Thumbnail',
2240
- filmstrip=filmstrip_thumbnail,
2241
- )
2109
+ for keyword in kwargs:
2110
+ if keyword not in ('display_name', 'tag_list', 'created_at'):
2111
+ raise TypeError(
2112
+ f"Shotgun.share_thumbnail() got an unexpected keyword argument '{keyword}'"
2113
+ )
2114
+ arguments = _files.UploadArguments(
2115
+ entity_type=share.source_entity_type,
2116
+ entity_id=share.source_entity_id,
2117
+ path=share.thumbnail_path,
2118
+ field_name=_files.thumbnail_fields(filmstrip_thumbnail)[0],
2119
+ display_name=cast('str | None', kwargs.get('display_name')),
2120
+ tag_list=cast('str | None', kwargs.get('tag_list')),
2121
+ created_at=cast('_dt_module.datetime | None', kwargs.get('created_at')),
2242
2122
  )
2123
+ prepared = _files.prepare_upload(_files.upload_options(arguments))
2243
2124
  context = config_module.snapshot(self._config)
2244
2125
  require_secure_rpc(context, include_auth=True)
2245
2126
  with self._driver_lease(context) as driver:
@@ -2247,9 +2128,7 @@ class Shotgun:
2247
2128
  attachment_id = self._upload_thumbnail_for(driver, context, prepared)
2248
2129
  if len(share.entities) == 1:
2249
2130
  return attachment_id
2250
- field_name: Literal['filmstrip_image', 'image'] = (
2251
- 'filmstrip_image' if filmstrip_thumbnail else 'image'
2252
- )
2131
+ field_name = _files.thumbnail_fields(filmstrip_thumbnail)[1]
2253
2132
  ready = driver.poll(
2254
2133
  lambda: self._thumbnail_source_ready(
2255
2134
  driver,
@@ -2279,10 +2158,6 @@ class Shotgun:
2279
2158
  filmstrip_thumbnail,
2280
2159
  )
2281
2160
 
2282
- @overload
2283
- def summarize(self, entity_type: Never) -> dict[str, object]: ...
2284
-
2285
- @overload
2286
2161
  def summarize(
2287
2162
  self,
2288
2163
  entity_type: str,
@@ -2291,29 +2166,39 @@ class Shotgun:
2291
2166
  filter_operator: str | None = None,
2292
2167
  grouping: list[dict[str, Any]] | None = None,
2293
2168
  include_archived_projects: bool = True,
2294
- ) -> dict[str, object]: ...
2295
-
2296
- @_methods.expose_summarize_signature
2297
- def summarize(self, *args: object, **kwargs: object) -> dict[str, object]:
2169
+ ) -> dict[str, object]:
2298
2170
  """Summarize field data returned by a query.
2299
2171
 
2300
2172
  Args:
2301
- *args: Positional values from the documented `summarize` signature.
2302
- **kwargs: Keyword values from the documented `summarize` signature.
2173
+ entity_type: Entity type to summarize.
2174
+ filters: Filters in list or dict syntax.
2175
+ summary_fields: List of `{'field': ..., 'type': ...}` summaries.
2176
+ filter_operator: `'all'` or `'any'` for list-syntax filters.
2177
+ grouping: Optional grouping specs.
2178
+ include_archived_projects: Include archived projects when `True`.
2303
2179
 
2304
2180
  Returns:
2305
2181
  Dict with `summaries` and (when grouped) `groups`.
2306
2182
 
2307
2183
  Raises:
2308
- TypeError: If arguments do not match the documented signature.
2309
2184
  ShotgunError: If an RPC fault or terminal transport failure occurs,
2310
2185
  or if `filters`, `summary_fields`, or the server result has the
2311
2186
  wrong type.
2312
2187
  ProtocolError: If a server request returns an HTTP error.
2313
2188
  ValueError: If authenticated HTTP violates the configured origin
2314
- policy.
2189
+ policy, or a naive datetime cannot be converted to UTC.
2190
+ OverflowError: If converting an aware datetime to UTC exceeds the
2191
+ supported datetime range.
2192
+ OSError: If the platform cannot resolve a local datetime.
2315
2193
  """
2316
- options = _methods.bind_summarize(args, kwargs, 'Shotgun')
2194
+ options = _methods.SummarizeOptions(
2195
+ entity_type,
2196
+ filters,
2197
+ summary_fields,
2198
+ filter_operator,
2199
+ grouping,
2200
+ include_archived_projects,
2201
+ )
2317
2202
  params = _methods.build_summarize_params(options)
2318
2203
  return _crud.require_dictionary_result(self._call('summarize', params), 'summarize')
2319
2204
 
@@ -2324,7 +2209,7 @@ class Shotgun:
2324
2209
  project_ids: list[int] | None = None,
2325
2210
  limit: int | None = None,
2326
2211
  ) -> dict[str, object]:
2327
- """Search entity display names matching `text` (min 3 chars server-side).
2212
+ """Search entity display names matching `text`.
2328
2213
 
2329
2214
  Args:
2330
2215
  text: Text to match.
@@ -2338,10 +2223,14 @@ class Shotgun:
2338
2223
 
2339
2224
  Raises:
2340
2225
  ShotgunError: If an RPC fault or terminal transport failure occurs,
2341
- or if `entity_types` or the server result is not a dictionary.
2226
+ if `entity_types` or the server result is not a dictionary, or
2227
+ if an entity filter is invalid.
2342
2228
  ProtocolError: If a server request returns an HTTP error.
2343
2229
  ValueError: If authenticated HTTP violates the configured origin
2344
- policy.
2230
+ policy, or a naive datetime cannot be converted to UTC.
2231
+ OverflowError: If converting an aware datetime to UTC exceeds the
2232
+ supported datetime range.
2233
+ OSError: If the platform cannot resolve a local datetime.
2345
2234
  """
2346
2235
  params = _methods.build_text_search_params(
2347
2236
  text,
@@ -2409,7 +2298,10 @@ class Shotgun:
2409
2298
  nonempty file. A thumbnail failure occurs after the update.
2410
2299
  ProtocolError: If a server request returns an HTTP error.
2411
2300
  ValueError: If authenticated HTTP violates the configured origin
2412
- policy.
2301
+ policy, or a naive datetime cannot be converted to UTC.
2302
+ OverflowError: If converting an aware datetime to UTC exceeds the
2303
+ supported datetime range.
2304
+ OSError: If the platform cannot resolve a local datetime.
2413
2305
  """
2414
2306
  field_data, thumbnails = _files.split_thumbnail_uploads(data, keep_image_none=True)
2415
2307
  params = _crud.build_update_params(
@@ -2438,7 +2330,8 @@ class Shotgun:
2438
2330
  user: Optional user entity to attribute the access to.
2439
2331
 
2440
2332
  Raises:
2441
- ShotgunError: If an RPC fault or terminal transport failure occurs.
2333
+ ShotgunError: If a project or user ID is invalid, or an RPC fault
2334
+ or terminal transport failure occurs.
2442
2335
  ProtocolError: If a server request returns an HTTP error.
2443
2336
  ValueError: If authenticated HTTP violates the configured origin
2444
2337
  policy.
@@ -2451,10 +2344,6 @@ class Shotgun:
2451
2344
  ),
2452
2345
  )
2453
2346
 
2454
- @overload
2455
- def upload(self, entity_type: Never) -> int: ...
2456
-
2457
- @overload
2458
2347
  def upload(
2459
2348
  self,
2460
2349
  entity_type: str,
@@ -2464,25 +2353,26 @@ class Shotgun:
2464
2353
  display_name: str | None = None,
2465
2354
  tag_list: str | None = None,
2466
2355
  created_at: _dt_module.datetime | None = None,
2467
- ) -> int: ...
2468
-
2469
- @_expose_upload_signature
2470
- def upload(self, *args: object, **kwargs: object) -> int:
2356
+ ) -> int:
2471
2357
  """Upload a file or thumbnail and return its Attachment ID.
2472
2358
 
2473
2359
  A server requiring legacy form uploads raises `ShotgunError` before any
2474
2360
  upload request is sent. Server capability reads may occur.
2475
2361
 
2476
2362
  Args:
2477
- *args: Positional arguments from the documented upload signature;
2478
- the path may be text or filesystem-encoded bytes.
2479
- **kwargs: Keyword arguments from the documented upload signature.
2363
+ entity_type: Entity type receiving the upload.
2364
+ entity_id: Numeric identifier of the receiving entity.
2365
+ path: Existing non-empty file path as text or filesystem-encoded
2366
+ bytes.
2367
+ field_name: Optional file field receiving an Attachment.
2368
+ display_name: Optional Attachment display name.
2369
+ tag_list: Optional comma-delimited Attachment tags.
2370
+ created_at: Optional Attachment creation timestamp.
2480
2371
 
2481
2372
  Returns:
2482
2373
  Created Attachment ID.
2483
2374
 
2484
2375
  Raises:
2485
- TypeError: If arguments do not match the documented signature.
2486
2376
  ShotgunError: If the file or an upload response is invalid, an RPC
2487
2377
  fault or terminal transport failure occurs, or the server
2488
2378
  requires legacy form uploads.
@@ -2490,23 +2380,10 @@ class Shotgun:
2490
2380
  ValueError: If authenticated HTTP violates the configured origin
2491
2381
  policy.
2492
2382
  """
2493
- arguments = _bind_upload(args, kwargs)
2494
- thumbnail_fields = {
2495
- 'filmstrip_image',
2496
- 'filmstrip_thumb_image',
2497
- 'image',
2498
- 'thumb_image',
2499
- }
2500
- upload_type: Literal['Attachment', 'Thumbnail'] = (
2501
- 'Thumbnail' if arguments.field_name in thumbnail_fields else 'Attachment'
2502
- )
2503
- return self._upload(
2504
- _files.UploadOptions(
2505
- arguments=arguments,
2506
- upload_type=upload_type,
2507
- filmstrip=arguments.field_name in {'filmstrip_image', 'filmstrip_thumb_image'},
2508
- )
2383
+ arguments = _files.UploadArguments(
2384
+ entity_type, entity_id, path, field_name, display_name, tag_list, created_at
2509
2385
  )
2386
+ return self._upload(_files.upload_options(arguments))
2510
2387
 
2511
2388
  def upload_filmstrip_thumbnail(
2512
2389
  self,
@@ -2672,10 +2549,6 @@ class Shotgun:
2672
2549
  'work_schedule_read',
2673
2550
  )
2674
2551
 
2675
- @overload
2676
- def work_schedule_update(self, date: Never) -> dict[str, object]: ...
2677
-
2678
- @overload
2679
2552
  def work_schedule_update(
2680
2553
  self,
2681
2554
  date: _dt_module.date | str,
@@ -2684,30 +2557,31 @@ class Shotgun:
2684
2557
  project: dict[str, Any] | None = None,
2685
2558
  user: dict[str, Any] | None = None,
2686
2559
  recalculate_field: str | None = None,
2687
- ) -> dict[str, object]: ...
2688
-
2689
- @_methods.expose_work_schedule_update_signature
2690
- def work_schedule_update(self, *args: object, **kwargs: object) -> dict[str, object]:
2691
- """Update a work schedule using the documented arguments.
2560
+ ) -> dict[str, object]:
2561
+ """Update a work schedule.
2692
2562
 
2693
2563
  Args:
2694
- *args: Positional values from the documented `work_schedule_update`
2695
- signature.
2696
- **kwargs: Keyword values from the documented `work_schedule_update`
2697
- signature.
2564
+ date: Date or exact `YYYY-MM-DD` string to update.
2565
+ working: Whether the date is a working day.
2566
+ description: Optional schedule description.
2567
+ project: Optional project entity restriction.
2568
+ user: Optional user entity restriction.
2569
+ recalculate_field: Optional field to recalculate.
2698
2570
 
2699
2571
  Returns:
2700
2572
  Updated work schedule data returned by the server.
2701
2573
 
2702
2574
  Raises:
2703
- TypeError: If arguments do not match the documented signature.
2704
2575
  ShotgunError: If an RPC fault or terminal transport failure occurs,
2705
- or if dates, exclusivity, or the server result is invalid.
2576
+ or if the date, project/user exclusivity, or server result is
2577
+ invalid.
2706
2578
  ProtocolError: If a server request returns an HTTP error.
2707
2579
  ValueError: If authenticated HTTP violates the configured origin
2708
2580
  policy.
2709
2581
  """
2710
- options = _methods.bind_work_schedule_update(args, kwargs, 'Shotgun')
2582
+ options = _methods.WorkScheduleUpdateOptions(
2583
+ date, working, description, project, user, recalculate_field
2584
+ )
2711
2585
  params = _methods.build_work_schedule_update_params(options)
2712
2586
  return _crud.require_dictionary_result(
2713
2587
  self._call('work_schedule_update', params),