sg-python-api 0.1.1__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.
- {sg_python_api-0.1.1.dist-info → sg_python_api-0.2.0.dist-info}/METADATA +21 -24
- sg_python_api-0.2.0.dist-info/RECORD +58 -0
- shotgun_api3/__init__.py +4 -0
- shotgun_api3/_async_client.py +404 -316
- shotgun_api3/_async_driver.py +25 -55
- shotgun_api3/_async_httpx2.py +92 -59
- shotgun_api3/_async_transport.py +38 -3
- shotgun_api3/_capabilities.py +4 -3
- shotgun_api3/_client.py +354 -462
- shotgun_api3/_config.py +245 -120
- shotgun_api3/_connections.py +144 -0
- shotgun_api3/_crud.py +25 -250
- shotgun_api3/_driver.py +29 -54
- shotgun_api3/_execution.py +1 -3
- shotgun_api3/_files.py +215 -123
- shotgun_api3/_methods.py +153 -412
- shotgun_api3/_protocol.py +27 -146
- shotgun_api3/_transport.py +82 -407
- shotgun_api3/_version.py +7 -2
- shotgun_api3/lib/mockgun/__init__.py +20 -7
- shotgun_api3/lib/mockgun/_activity.py +510 -0
- shotgun_api3/lib/mockgun/_async_client.py +196 -0
- shotgun_api3/lib/mockgun/_dates.py +164 -0
- shotgun_api3/lib/mockgun/_errors.py +18 -0
- shotgun_api3/lib/mockgun/_events.py +1031 -0
- shotgun_api3/lib/mockgun/_follows.py +217 -0
- shotgun_api3/lib/mockgun/_handlers.py +558 -0
- shotgun_api3/lib/mockgun/_links.py +440 -0
- shotgun_api3/lib/mockgun/_new_client.py +92 -0
- shotgun_api3/lib/mockgun/_presets.py +492 -0
- shotgun_api3/lib/mockgun/_query.py +2163 -0
- shotgun_api3/lib/mockgun/_retirement.py +368 -0
- shotgun_api3/lib/mockgun/_schema.py +172 -0
- shotgun_api3/lib/mockgun/_schema_writes.py +425 -0
- shotgun_api3/lib/mockgun/_search.py +378 -0
- shotgun_api3/lib/mockgun/_server.py +169 -0
- shotgun_api3/lib/mockgun/_sessions.py +206 -0
- shotgun_api3/lib/mockgun/_site.py +150 -0
- shotgun_api3/lib/mockgun/_site_data.py +584 -0
- shotgun_api3/lib/mockgun/_storage.py +208 -0
- shotgun_api3/lib/mockgun/_store.py +1446 -0
- shotgun_api3/lib/mockgun/_summaries.py +964 -0
- shotgun_api3/lib/mockgun/_sync_client.py +190 -0
- shotgun_api3/lib/mockgun/_threads.py +226 -0
- shotgun_api3/lib/mockgun/_thumbnails.py +146 -0
- shotgun_api3/lib/mockgun/_transport.py +145 -0
- shotgun_api3/lib/mockgun/_uploads.py +417 -0
- shotgun_api3/lib/mockgun/_wire.py +120 -0
- shotgun_api3/lib/mockgun/mockgun.py +14 -0
- shotgun_api3/lib/sgtimezone.py +24 -1
- sg_python_api-0.1.1.dist-info/RECORD +0 -31
- shotgun_api3/_signatures.py +0 -53
- shotgun_api3/lib/mockgun/_async.py +0 -315
- shotgun_api3/lib/mockgun/_mockgun.py +0 -1666
- {sg_python_api-0.1.1.dist-info → sg_python_api-0.2.0.dist-info}/WHEEL +0 -0
- {sg_python_api-0.1.1.dist-info → sg_python_api-0.2.0.dist-info}/licenses/LICENSE +0 -0
shotgun_api3/_driver.py
CHANGED
|
@@ -33,7 +33,6 @@ from ._execution import (
|
|
|
33
33
|
build_rpc_request,
|
|
34
34
|
)
|
|
35
35
|
from ._transport import (
|
|
36
|
-
ReadTimeoutError,
|
|
37
36
|
SendError,
|
|
38
37
|
TransportError,
|
|
39
38
|
TransportRequest,
|
|
@@ -46,23 +45,15 @@ _HTTP_REDIRECT = 302
|
|
|
46
45
|
|
|
47
46
|
@dataclasses.dataclass(frozen=True)
|
|
48
47
|
class AttemptControl:
|
|
49
|
-
"""Consume attempt bytes
|
|
48
|
+
"""Consume attempt bytes with progress reporting."""
|
|
50
49
|
|
|
51
50
|
_attempt: int
|
|
52
|
-
_clock: Callable[[], float]
|
|
53
|
-
_deadline: float | None
|
|
54
51
|
_observer: ObserverCallback | None
|
|
55
52
|
_operation: _protocol.OperationCategory
|
|
56
53
|
_route: EventRoute
|
|
57
54
|
|
|
58
|
-
def
|
|
59
|
-
"""Raise when cumulative monotonic attempt time exceeds its limit."""
|
|
60
|
-
if self._deadline is not None and self._clock() > self._deadline:
|
|
61
|
-
raise ReadTimeoutError('total attempt timeout exceeded')
|
|
62
|
-
|
|
63
|
-
def _consume_buffered(self, body: bytes, total_bytes: int | None = None) -> bytes:
|
|
55
|
+
def consume_buffered(self, body: bytes, total_bytes: int | None = None) -> bytes:
|
|
64
56
|
"""Observe a received buffered body without rebuilding it."""
|
|
65
|
-
self._check_deadline()
|
|
66
57
|
if body:
|
|
67
58
|
notify_observer(
|
|
68
59
|
self._observer,
|
|
@@ -76,22 +67,13 @@ class AttemptControl:
|
|
|
76
67
|
)
|
|
77
68
|
return body
|
|
78
69
|
|
|
79
|
-
def _remaining_timeout(self) -> float | None:
|
|
80
|
-
"""Return the remaining cumulative attempt timeout."""
|
|
81
|
-
if self._deadline is None:
|
|
82
|
-
return None
|
|
83
|
-
remaining = self._deadline - self._clock()
|
|
84
|
-
if remaining < 0:
|
|
85
|
-
raise ReadTimeoutError('total attempt timeout exceeded')
|
|
86
|
-
return remaining
|
|
87
|
-
|
|
88
70
|
def consume_into(
|
|
89
71
|
self,
|
|
90
72
|
chunks: Iterable[bytes],
|
|
91
73
|
sink: Callable[[bytes], object],
|
|
92
74
|
total_bytes: int | None = None,
|
|
93
75
|
) -> None:
|
|
94
|
-
"""Write chunks while
|
|
76
|
+
"""Write chunks while reporting progress.
|
|
95
77
|
|
|
96
78
|
Args:
|
|
97
79
|
chunks: Fresh streaming body chunks for this attempt.
|
|
@@ -99,14 +81,12 @@ class AttemptControl:
|
|
|
99
81
|
total_bytes: Known complete body size, when available.
|
|
100
82
|
|
|
101
83
|
Raises:
|
|
102
|
-
|
|
103
|
-
limit.
|
|
84
|
+
Exception: If reading a chunk or invoking the sink fails.
|
|
104
85
|
"""
|
|
105
86
|
transferred = 0
|
|
106
87
|
for chunk in chunks:
|
|
107
88
|
sink(chunk)
|
|
108
89
|
transferred += len(chunk)
|
|
109
|
-
self._check_deadline()
|
|
110
90
|
if chunk:
|
|
111
91
|
notify_observer(
|
|
112
92
|
self._observer,
|
|
@@ -120,7 +100,7 @@ class AttemptControl:
|
|
|
120
100
|
)
|
|
121
101
|
|
|
122
102
|
def consume(self, chunks: Iterable[bytes], total_bytes: int | None = None) -> bytes:
|
|
123
|
-
"""Collect chunks while
|
|
103
|
+
"""Collect chunks while reporting progress.
|
|
124
104
|
|
|
125
105
|
Args:
|
|
126
106
|
chunks: Fresh buffered or streaming body chunks for this attempt.
|
|
@@ -130,8 +110,7 @@ class AttemptControl:
|
|
|
130
110
|
Concatenated response bytes.
|
|
131
111
|
|
|
132
112
|
Raises:
|
|
133
|
-
|
|
134
|
-
limit.
|
|
113
|
+
Exception: If reading a chunk fails.
|
|
135
114
|
"""
|
|
136
115
|
body = BytesIO()
|
|
137
116
|
self.consume_into(chunks, body.write, total_bytes=total_bytes)
|
|
@@ -265,7 +244,13 @@ class _ObservedRequestBody:
|
|
|
265
244
|
raise
|
|
266
245
|
|
|
267
246
|
def close(self) -> None:
|
|
268
|
-
"""Close the source
|
|
247
|
+
"""Close the source iterator at most once.
|
|
248
|
+
|
|
249
|
+
Later calls do not repeat cleanup even when the first close fails.
|
|
250
|
+
|
|
251
|
+
Raises:
|
|
252
|
+
Exception: If the source iterator's cleanup fails.
|
|
253
|
+
"""
|
|
269
254
|
if self._closed:
|
|
270
255
|
return
|
|
271
256
|
self._closed = True
|
|
@@ -351,11 +336,8 @@ class Driver:
|
|
|
351
336
|
) -> AttemptState[AttemptControl]:
|
|
352
337
|
"""Build immutable timing and observer state for one fresh attempt."""
|
|
353
338
|
started = self._clock()
|
|
354
|
-
deadline = None if context.timeout.total is None else started + context.timeout.total
|
|
355
339
|
control = AttemptControl(
|
|
356
340
|
_attempt=attempt,
|
|
357
|
-
_clock=self._clock,
|
|
358
|
-
_deadline=deadline,
|
|
359
341
|
_observer=context.on_progress,
|
|
360
342
|
_operation=operation.category,
|
|
361
343
|
_route=route,
|
|
@@ -393,7 +375,6 @@ class Driver:
|
|
|
393
375
|
response_status = response.status
|
|
394
376
|
response_headers = response.headers
|
|
395
377
|
correlation_id = response_correlation_id(response_headers)
|
|
396
|
-
state.control._check_deadline() # noqa: SLF001 # pyright: ignore[reportPrivateUsage]
|
|
397
378
|
failure = _protocol.classify_http_failure(
|
|
398
379
|
response_status,
|
|
399
380
|
response_headers,
|
|
@@ -475,7 +456,7 @@ class Driver:
|
|
|
475
456
|
buffered.headers,
|
|
476
457
|
buffered.content,
|
|
477
458
|
)
|
|
478
|
-
body = control.
|
|
459
|
+
body = control.consume_buffered(
|
|
479
460
|
buffered.content,
|
|
480
461
|
total_bytes=len(buffered.content),
|
|
481
462
|
)
|
|
@@ -569,7 +550,6 @@ class Driver:
|
|
|
569
550
|
request: TransportRequest,
|
|
570
551
|
state: AttemptState[AttemptControl],
|
|
571
552
|
*,
|
|
572
|
-
redirected: bool,
|
|
573
553
|
streaming: bool,
|
|
574
554
|
) -> tuple[LiveResponse, '_TerminalResponseBoundary'] | AttemptRetry:
|
|
575
555
|
"""Send once and keep a received response through body cleanup."""
|
|
@@ -579,8 +559,6 @@ class Driver:
|
|
|
579
559
|
try:
|
|
580
560
|
try:
|
|
581
561
|
with exception_capture:
|
|
582
|
-
if redirected:
|
|
583
|
-
request = _request_with_remaining_timeout(request, state.control)
|
|
584
562
|
if streaming:
|
|
585
563
|
response = self._transport.send_streaming(request)
|
|
586
564
|
else:
|
|
@@ -635,6 +613,10 @@ class Driver:
|
|
|
635
613
|
Raises:
|
|
636
614
|
TypeError: If the RPC transport response lacks the buffered
|
|
637
615
|
response contract (`reason` and `content`).
|
|
616
|
+
TransportError: If a terminal or exhausted transport failure
|
|
617
|
+
occurs.
|
|
618
|
+
Exception: If request construction, status validation, response
|
|
619
|
+
consumption, or cleanup fails.
|
|
638
620
|
"""
|
|
639
621
|
normalized_params = copy.deepcopy(params)
|
|
640
622
|
options = ExecutionOptions(logical_method_name=_logical_method_name)
|
|
@@ -667,6 +649,11 @@ class Driver:
|
|
|
667
649
|
|
|
668
650
|
Yields:
|
|
669
651
|
Decoded RPC results in page order.
|
|
652
|
+
|
|
653
|
+
Raises:
|
|
654
|
+
TransportError: If a page exhausts its transport attempts.
|
|
655
|
+
Exception: If building, validating, decoding, or cleaning up a page
|
|
656
|
+
operation fails.
|
|
670
657
|
"""
|
|
671
658
|
if context.page_prefetch <= 0:
|
|
672
659
|
page = first_page
|
|
@@ -718,9 +705,8 @@ class Driver:
|
|
|
718
705
|
context: Immutable operation configuration.
|
|
719
706
|
request_factory: Builder called once for every fresh attempt.
|
|
720
707
|
consumer: Buffered or streaming response consumer using the
|
|
721
|
-
supplied
|
|
722
|
-
options:
|
|
723
|
-
compatibility-warning controls.
|
|
708
|
+
supplied progress control.
|
|
709
|
+
options: Redirect, streaming, and observer controls.
|
|
724
710
|
|
|
725
711
|
Returns:
|
|
726
712
|
The successful consumer result.
|
|
@@ -728,7 +714,8 @@ class Driver:
|
|
|
728
714
|
Raises:
|
|
729
715
|
TransportError: If a terminal or exhausted transport failure
|
|
730
716
|
occurs.
|
|
731
|
-
Exception: If
|
|
717
|
+
Exception: If request or redirect construction, status validation,
|
|
718
|
+
response consumption, or cleanup fails.
|
|
732
719
|
"""
|
|
733
720
|
execution_options = options or ExecutionOptions()
|
|
734
721
|
self._emit_warnings(context)
|
|
@@ -742,7 +729,6 @@ class Driver:
|
|
|
742
729
|
sent = self._send_request(
|
|
743
730
|
request,
|
|
744
731
|
state,
|
|
745
|
-
redirected=redirected,
|
|
746
732
|
streaming=execution_options.streaming,
|
|
747
733
|
)
|
|
748
734
|
if isinstance(sent, AttemptRetry):
|
|
@@ -786,6 +772,7 @@ class Driver:
|
|
|
786
772
|
Raises:
|
|
787
773
|
ValueError: If `attempts` is not positive or `interval` is
|
|
788
774
|
negative.
|
|
775
|
+
Exception: If the predicate or configured sleep fails.
|
|
789
776
|
"""
|
|
790
777
|
if attempts <= 0:
|
|
791
778
|
raise ValueError('poll attempts must be positive')
|
|
@@ -817,13 +804,12 @@ def _observe_request_body(
|
|
|
817
804
|
total_bytes: int | None,
|
|
818
805
|
state: AttemptState[AttemptControl],
|
|
819
806
|
) -> Generator[bytes, None, None]:
|
|
820
|
-
"""Yield request chunks with attempt
|
|
807
|
+
"""Yield request chunks with attempt progress reporting."""
|
|
821
808
|
transferred = 0
|
|
822
809
|
for chunk in chunks:
|
|
823
810
|
yield chunk
|
|
824
811
|
transferred += len(chunk)
|
|
825
812
|
if chunk:
|
|
826
|
-
state.control._check_deadline() # noqa: SLF001 # pyright: ignore[reportPrivateUsage]
|
|
827
813
|
notify_observer(
|
|
828
814
|
state.context.on_progress,
|
|
829
815
|
ProgressEvent(
|
|
@@ -852,14 +838,3 @@ def _request_with_progress(
|
|
|
852
838
|
state,
|
|
853
839
|
),
|
|
854
840
|
)
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
def _request_with_remaining_timeout(
|
|
858
|
-
request: TransportRequest,
|
|
859
|
-
control: AttemptControl,
|
|
860
|
-
) -> TransportRequest:
|
|
861
|
-
"""Restrict a later hop to its remaining cumulative attempt timeout."""
|
|
862
|
-
return _transport.narrow_total_timeout(
|
|
863
|
-
request,
|
|
864
|
-
control._remaining_timeout(), # noqa: SLF001 # pyright: ignore[reportPrivateUsage]
|
|
865
|
-
)
|
shotgun_api3/_execution.py
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
"""Share pure execution, attempt, and lease logic between the sync and async twins."""
|
|
2
2
|
|
|
3
3
|
import dataclasses
|
|
4
|
-
import uuid
|
|
5
4
|
from collections.abc import Callable, Mapping
|
|
6
5
|
from typing import Generic, Literal, TypeVar
|
|
7
6
|
|
|
@@ -35,7 +34,7 @@ class AttemptState(Generic[_ControlT_co]):
|
|
|
35
34
|
|
|
36
35
|
Attributes:
|
|
37
36
|
context: Configuration captured for the logical operation.
|
|
38
|
-
control: Attempt-local
|
|
37
|
+
control: Attempt-local consumption and progress control.
|
|
39
38
|
number: One-based attempt number.
|
|
40
39
|
operation: Logical operation and replay classification.
|
|
41
40
|
route: Observer route for the attempt.
|
|
@@ -235,7 +234,6 @@ def build_rpc_request(
|
|
|
235
234
|
headers=headers,
|
|
236
235
|
body=_protocol.encode_payload(payload),
|
|
237
236
|
timeout=context.timeout,
|
|
238
|
-
token=uuid.uuid4().hex,
|
|
239
237
|
)
|
|
240
238
|
|
|
241
239
|
|