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.
- {sg_python_api-0.1.2.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 +347 -281
- 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 +296 -422
- 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 +193 -21
- shotgun_api3/_methods.py +59 -411
- shotgun_api3/_protocol.py +27 -53
- 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.2.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.2.dist-info → sg_python_api-0.2.0.dist-info}/WHEEL +0 -0
- {sg_python_api-0.1.2.dist-info → sg_python_api-0.2.0.dist-info}/licenses/LICENSE +0 -0
shotgun_api3/_async_client.py
CHANGED
|
@@ -1,18 +1,13 @@
|
|
|
1
1
|
"""Native-async Shotgun client and lifecycle."""
|
|
2
2
|
|
|
3
|
-
# Same-file collaboration follows module-boundary privacy.
|
|
4
|
-
# pyright: reportPrivateUsage=false
|
|
5
|
-
|
|
6
3
|
import asyncio
|
|
7
4
|
import copy
|
|
8
5
|
import dataclasses
|
|
9
6
|
import datetime as _dt_module
|
|
10
7
|
import importlib
|
|
11
|
-
import inspect
|
|
12
8
|
import os
|
|
13
9
|
import threading
|
|
14
10
|
import urllib.request
|
|
15
|
-
import uuid
|
|
16
11
|
from collections.abc import AsyncGenerator, AsyncIterator, Callable, Mapping
|
|
17
12
|
from contextlib import ExitStack, asynccontextmanager, suppress
|
|
18
13
|
from pathlib import Path
|
|
@@ -21,6 +16,7 @@ from typing import (
|
|
|
21
16
|
TYPE_CHECKING,
|
|
22
17
|
Any,
|
|
23
18
|
BinaryIO,
|
|
19
|
+
ClassVar,
|
|
24
20
|
Literal,
|
|
25
21
|
Never,
|
|
26
22
|
ParamSpec,
|
|
@@ -43,12 +39,8 @@ from ._async_driver import (
|
|
|
43
39
|
from ._async_transport import AsyncStreamingResponse, AsyncTransport
|
|
44
40
|
from ._capabilities import ClientCapabilities, ServerCapabilities
|
|
45
41
|
from ._client import (
|
|
46
|
-
CONSTRUCTOR_SIGNATURE,
|
|
47
42
|
ConstructorOptions,
|
|
48
|
-
Shotgun,
|
|
49
|
-
bind_constructor,
|
|
50
43
|
build_config,
|
|
51
|
-
expose_constructor_signature,
|
|
52
44
|
)
|
|
53
45
|
from ._config import (
|
|
54
46
|
Config,
|
|
@@ -60,7 +52,6 @@ from ._config import (
|
|
|
60
52
|
server_page_limit,
|
|
61
53
|
)
|
|
62
54
|
from ._execution import SuppressedExceptionCapture, release_lease, retire_transport
|
|
63
|
-
from ._signatures import bind_arguments, expose_signature
|
|
64
55
|
from ._transport import (
|
|
65
56
|
ReadTimeoutError,
|
|
66
57
|
SendError,
|
|
@@ -80,11 +71,6 @@ _FilesystemParams = ParamSpec('_FilesystemParams')
|
|
|
80
71
|
_FilesystemResult = TypeVar('_FilesystemResult')
|
|
81
72
|
_UploadResult = TypeVar('_UploadResult')
|
|
82
73
|
|
|
83
|
-
_METHOD_UPLOAD_SIGNATURE = inspect.signature(Shotgun.upload)
|
|
84
|
-
_UPLOAD_SIGNATURE = inspect.signature(Shotgun.upload).replace(
|
|
85
|
-
parameters=tuple(inspect.signature(Shotgun.upload).parameters.values())[1:]
|
|
86
|
-
)
|
|
87
|
-
|
|
88
74
|
|
|
89
75
|
class _AsyncDownloadCancellationScope:
|
|
90
76
|
"""Own and join every filesystem worker started by one transfer."""
|
|
@@ -94,12 +80,17 @@ class _AsyncDownloadCancellationScope:
|
|
|
94
80
|
self._captured_cancellation: asyncio.CancelledError | None = None
|
|
95
81
|
|
|
96
82
|
@property
|
|
97
|
-
def
|
|
83
|
+
def cancellation(self) -> asyncio.CancelledError | None:
|
|
98
84
|
"""Return the first cancellation captured while joining a worker."""
|
|
99
85
|
return self._captured_cancellation
|
|
100
86
|
|
|
101
|
-
def
|
|
102
|
-
"""Retain one actively delivered caller cancellation.
|
|
87
|
+
def capture_cancellation(self, error: BaseException) -> None:
|
|
88
|
+
"""Retain one actively delivered caller cancellation.
|
|
89
|
+
|
|
90
|
+
Args:
|
|
91
|
+
error: Failure to retain if it is an active caller cancellation;
|
|
92
|
+
other failures are ignored.
|
|
93
|
+
"""
|
|
103
94
|
current = asyncio.current_task()
|
|
104
95
|
if (
|
|
105
96
|
not isinstance(error, asyncio.CancelledError)
|
|
@@ -111,28 +102,45 @@ class _AsyncDownloadCancellationScope:
|
|
|
111
102
|
self._captured_cancellation = error
|
|
112
103
|
current.uncancel()
|
|
113
104
|
|
|
114
|
-
def
|
|
115
|
-
"""Propagate a captured cancellation only between worker phases.
|
|
105
|
+
def raise_if_cancelled(self) -> None:
|
|
106
|
+
"""Propagate a captured cancellation only between worker phases.
|
|
107
|
+
|
|
108
|
+
Raises:
|
|
109
|
+
asyncio.CancelledError: If caller cancellation was captured.
|
|
110
|
+
"""
|
|
116
111
|
if self._captured_cancellation is not None:
|
|
117
112
|
raise self._captured_cancellation
|
|
118
113
|
|
|
119
|
-
async def
|
|
114
|
+
async def run(
|
|
120
115
|
self,
|
|
121
116
|
function: Callable[_FilesystemParams, _FilesystemResult],
|
|
122
117
|
*args: _FilesystemParams.args,
|
|
123
118
|
**kwargs: _FilesystemParams.kwargs,
|
|
124
119
|
) -> _FilesystemResult:
|
|
125
|
-
"""Run one worker to completion while retaining cancellation.
|
|
120
|
+
"""Run one worker to completion while retaining cancellation.
|
|
121
|
+
|
|
122
|
+
Args:
|
|
123
|
+
function: Blocking operation to execute off-loop.
|
|
124
|
+
*args: Positional arguments forwarded to the operation.
|
|
125
|
+
**kwargs: Keyword arguments forwarded to the operation.
|
|
126
|
+
|
|
127
|
+
Returns:
|
|
128
|
+
The operation's result after its worker finishes.
|
|
129
|
+
|
|
130
|
+
Raises:
|
|
131
|
+
BaseException: If the worker fails. A captured caller cancellation
|
|
132
|
+
takes precedence over the worker's failure.
|
|
133
|
+
"""
|
|
126
134
|
task = asyncio.create_task(asyncio.to_thread(function, *args, **kwargs))
|
|
127
135
|
while not task.done():
|
|
128
136
|
try:
|
|
129
137
|
await asyncio.wait({task})
|
|
130
138
|
except asyncio.CancelledError as error:
|
|
131
|
-
self.
|
|
139
|
+
self.capture_cancellation(error)
|
|
132
140
|
try:
|
|
133
141
|
return task.result()
|
|
134
142
|
except BaseException:
|
|
135
|
-
self.
|
|
143
|
+
self.raise_if_cancelled()
|
|
136
144
|
raise
|
|
137
145
|
|
|
138
146
|
|
|
@@ -155,7 +163,7 @@ class _AsyncDownloadCleanupCapture:
|
|
|
155
163
|
_traceback: TracebackType | None,
|
|
156
164
|
) -> bool:
|
|
157
165
|
"""Capture and suppress one async download-cleanup exception."""
|
|
158
|
-
self.exception = self._workers.
|
|
166
|
+
self.exception = self._workers.cancellation or exception
|
|
159
167
|
return exception is not None
|
|
160
168
|
|
|
161
169
|
|
|
@@ -193,35 +201,38 @@ class _AsyncDownloadExchange:
|
|
|
193
201
|
workers = _AsyncDownloadCancellationScope()
|
|
194
202
|
try:
|
|
195
203
|
try:
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
204
|
+
with _files.download_destination_io(destination):
|
|
205
|
+
stack, handle, temporary = await workers.run(
|
|
206
|
+
_open_download_temporary,
|
|
207
|
+
destination,
|
|
208
|
+
)
|
|
209
|
+
workers.raise_if_cancelled()
|
|
201
210
|
opened_handle = handle
|
|
202
211
|
|
|
203
212
|
async def write(chunk: bytes) -> None:
|
|
204
|
-
|
|
205
|
-
|
|
213
|
+
with _files.download_destination_io(destination):
|
|
214
|
+
await workers.run(opened_handle.write, chunk)
|
|
215
|
+
workers.raise_if_cancelled()
|
|
206
216
|
|
|
207
217
|
await control.consume_into(chunks, write, total_bytes=total_bytes)
|
|
208
|
-
|
|
209
|
-
|
|
218
|
+
with _files.download_destination_io(destination):
|
|
219
|
+
await workers.run(handle.flush)
|
|
220
|
+
workers.raise_if_cancelled()
|
|
210
221
|
close_exception = await _capture_download_cleanup(workers, stack.close)
|
|
211
|
-
stack = None
|
|
212
|
-
|
|
213
|
-
workers._raise_if_cancelled()
|
|
222
|
+
stack, handle = None, None
|
|
223
|
+
workers.raise_if_cancelled()
|
|
214
224
|
if close_exception is not None:
|
|
215
225
|
_raise_download_cleanup_error()
|
|
216
226
|
# This is the final cancellation checkpoint before atomic commit.
|
|
217
227
|
# Once `replace` starts, its worker is joined before cancellation
|
|
218
228
|
# propagates, so the destination cannot change after return.
|
|
219
229
|
await asyncio.sleep(0)
|
|
220
|
-
|
|
221
|
-
|
|
230
|
+
with _files.download_destination_io(destination):
|
|
231
|
+
await workers.run(temporary.replace, destination)
|
|
232
|
+
workers.raise_if_cancelled()
|
|
222
233
|
except BaseException as error:
|
|
223
234
|
operation_exception = error
|
|
224
|
-
workers.
|
|
235
|
+
workers.capture_cancellation(error)
|
|
225
236
|
raise
|
|
226
237
|
finally:
|
|
227
238
|
close_exception: BaseException | None = None
|
|
@@ -234,7 +245,7 @@ class _AsyncDownloadExchange:
|
|
|
234
245
|
temporary.unlink,
|
|
235
246
|
missing_ok=True,
|
|
236
247
|
)
|
|
237
|
-
workers.
|
|
248
|
+
workers.raise_if_cancelled()
|
|
238
249
|
if operation_exception is None:
|
|
239
250
|
if close_exception is not None:
|
|
240
251
|
raise _AsyncDownloadCleanupError from None
|
|
@@ -258,12 +269,23 @@ class _AsyncDownloadExchange:
|
|
|
258
269
|
chunks = streaming.aiter_chunks(_ASYNC_FILE_BATCH_SIZE)
|
|
259
270
|
return await self._write_file(chunks, control, total_bytes)
|
|
260
271
|
|
|
261
|
-
async def
|
|
272
|
+
async def execute(
|
|
262
273
|
self,
|
|
263
274
|
driver: AsyncDriver,
|
|
264
275
|
operation: _protocol.OperationDescriptor,
|
|
265
276
|
) -> bytes | str:
|
|
266
|
-
"""Execute the workflow through the common async attempt loop.
|
|
277
|
+
"""Execute the workflow through the common async attempt loop.
|
|
278
|
+
|
|
279
|
+
Returns:
|
|
280
|
+
Downloaded bytes, or the destination path for a file download.
|
|
281
|
+
|
|
282
|
+
Raises:
|
|
283
|
+
SendError: If required download cleanup fails.
|
|
284
|
+
TransportError: If the attempt loop cannot complete the transfer.
|
|
285
|
+
ShotgunFileDownloadError: If the response or redirect is invalid,
|
|
286
|
+
or creating, writing, flushing, or publishing the destination
|
|
287
|
+
file fails.
|
|
288
|
+
"""
|
|
267
289
|
try:
|
|
268
290
|
return await driver.execute(
|
|
269
291
|
operation,
|
|
@@ -310,36 +332,6 @@ class _TokenExchangeProgress:
|
|
|
310
332
|
token: str | None = None
|
|
311
333
|
|
|
312
334
|
|
|
313
|
-
def _bind_upload(
|
|
314
|
-
args: tuple[object, ...],
|
|
315
|
-
kwargs: dict[str, object],
|
|
316
|
-
) -> _files.UploadArguments:
|
|
317
|
-
"""Bind runtime arguments with upload-specific errors."""
|
|
318
|
-
bound = bind_arguments(_UPLOAD_SIGNATURE, args, kwargs, 'AsyncShotgun.upload()')
|
|
319
|
-
arguments = cast('Callable[..., _files.UploadArguments]', _files.UploadArguments)
|
|
320
|
-
return arguments(**bound.arguments)
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
async def _capture_download_cleanup(
|
|
324
|
-
workers: _AsyncDownloadCancellationScope,
|
|
325
|
-
function: Callable[_FilesystemParams, _FilesystemResult],
|
|
326
|
-
*args: _FilesystemParams.args,
|
|
327
|
-
**kwargs: _FilesystemParams.kwargs,
|
|
328
|
-
) -> BaseException | None:
|
|
329
|
-
"""Run one joined cleanup operation and capture its failure."""
|
|
330
|
-
capture = _AsyncDownloadCleanupCapture(workers)
|
|
331
|
-
with capture:
|
|
332
|
-
await workers._run(function, *args, **kwargs)
|
|
333
|
-
return capture.exception
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
def _expose_upload_signature(function: Callable[..., object]) -> Callable[..., object]:
|
|
337
|
-
"""Attach the implemented sync upload signature and annotations."""
|
|
338
|
-
expose_signature(function, _METHOD_UPLOAD_SIGNATURE, Shotgun.upload.__doc__)
|
|
339
|
-
object.__setattr__(function, '__annotations__', Shotgun.upload.__annotations__)
|
|
340
|
-
return function
|
|
341
|
-
|
|
342
|
-
|
|
343
335
|
class AsyncShotgun:
|
|
344
336
|
"""Provide the asynchronous Flow Production Tracking API client surface.
|
|
345
337
|
|
|
@@ -356,12 +348,8 @@ class AsyncShotgun:
|
|
|
356
348
|
server_info: Cached raw metadata for the configured origin.
|
|
357
349
|
"""
|
|
358
350
|
|
|
359
|
-
|
|
351
|
+
_in_process: ClassVar[bool] = False
|
|
360
352
|
|
|
361
|
-
@overload
|
|
362
|
-
def __init__(self, base_url: Never) -> None: ...
|
|
363
|
-
|
|
364
|
-
@overload
|
|
365
353
|
def __init__(
|
|
366
354
|
self,
|
|
367
355
|
base_url: str,
|
|
@@ -376,10 +364,7 @@ class AsyncShotgun:
|
|
|
376
364
|
sudo_as_login: str | None = None,
|
|
377
365
|
session_token: str | None = None,
|
|
378
366
|
auth_token: str | None = None,
|
|
379
|
-
) -> None:
|
|
380
|
-
|
|
381
|
-
@expose_constructor_signature
|
|
382
|
-
def __init__(self, *args: object, **kwargs: object) -> None:
|
|
367
|
+
) -> None:
|
|
383
368
|
"""Initialize a client for one site and authentication mode.
|
|
384
369
|
|
|
385
370
|
Supply exactly one authentication mode: `script_name` with `api_key`,
|
|
@@ -391,6 +376,8 @@ class AsyncShotgun:
|
|
|
391
376
|
`http_proxy` accepts `[user:pass@]host[:port]`, with port 8080 when
|
|
392
377
|
omitted. A truthy `ca_certs` selects a CA bundle; otherwise
|
|
393
378
|
`SHOTGUN_API_CACERTS` is consulted before using system trust roots.
|
|
379
|
+
`SHOTGUN_API_RETRY_INTERVAL` supplies a nonnegative integer retry
|
|
380
|
+
interval in milliseconds, defaulting to 3000.
|
|
394
381
|
|
|
395
382
|
Constructor validation is synchronous; readiness and authentication
|
|
396
383
|
exchange I/O begins only through an operation, `connect()`, or an async
|
|
@@ -398,20 +385,57 @@ class AsyncShotgun:
|
|
|
398
385
|
I/O.
|
|
399
386
|
|
|
400
387
|
Args:
|
|
401
|
-
|
|
402
|
-
|
|
388
|
+
base_url: Flow Production Tracking site URL.
|
|
389
|
+
script_name: Script name for script authentication.
|
|
390
|
+
api_key: API key for script authentication.
|
|
391
|
+
convert_datetimes_to_utc: Whether to convert datetime values to
|
|
392
|
+
UTC.
|
|
393
|
+
http_proxy: Optional proxy authority in `[user:pass@]host[:port]`
|
|
394
|
+
form.
|
|
395
|
+
connect: Whether to request eager readiness during asynchronous
|
|
396
|
+
context entry.
|
|
397
|
+
ca_certs: Optional certificate-authority bundle path.
|
|
398
|
+
login: Login for human-user authentication.
|
|
399
|
+
password: Password for human-user authentication.
|
|
400
|
+
sudo_as_login: Optional user login for sudo impersonation.
|
|
401
|
+
session_token: Existing session token for authentication.
|
|
402
|
+
auth_token: Optional two-factor authentication token.
|
|
403
403
|
|
|
404
404
|
Raises:
|
|
405
|
-
|
|
406
|
-
|
|
405
|
+
ValueError: If authentication, URL, or proxy values are invalid,
|
|
406
|
+
the configured CA bundle is not a readable file, or
|
|
407
|
+
`SHOTGUN_API_RETRY_INTERVAL` is not a nonnegative integer.
|
|
407
408
|
"""
|
|
408
|
-
self._initialize(
|
|
409
|
+
self._initialize(
|
|
410
|
+
ConstructorOptions(
|
|
411
|
+
base_url,
|
|
412
|
+
script_name,
|
|
413
|
+
api_key,
|
|
414
|
+
convert_datetimes_to_utc,
|
|
415
|
+
http_proxy,
|
|
416
|
+
connect,
|
|
417
|
+
ca_certs,
|
|
418
|
+
login,
|
|
419
|
+
password,
|
|
420
|
+
sudo_as_login,
|
|
421
|
+
session_token,
|
|
422
|
+
auth_token,
|
|
423
|
+
)
|
|
424
|
+
)
|
|
409
425
|
|
|
410
426
|
async def __aenter__(self) -> Self:
|
|
411
427
|
"""Enter this client and perform any configured eager connection.
|
|
412
428
|
|
|
413
429
|
Returns:
|
|
414
430
|
This client.
|
|
431
|
+
|
|
432
|
+
Raises:
|
|
433
|
+
ShotgunError: If eager connection encounters an RPC fault or
|
|
434
|
+
terminal transport failure.
|
|
435
|
+
ProtocolError: If an eager connection request returns an HTTP
|
|
436
|
+
error.
|
|
437
|
+
ValueError: If eager authenticated HTTP violates the configured
|
|
438
|
+
origin policy.
|
|
415
439
|
"""
|
|
416
440
|
if not self._connect_on_enter:
|
|
417
441
|
return self
|
|
@@ -427,6 +451,10 @@ class AsyncShotgun:
|
|
|
427
451
|
|
|
428
452
|
Args:
|
|
429
453
|
*exc: Context-manager exception details, if any.
|
|
454
|
+
|
|
455
|
+
Raises:
|
|
456
|
+
ShotgunError: If the built-in transport cannot finish cleanup.
|
|
457
|
+
Exception: If a replacement transport's cleanup fails.
|
|
430
458
|
"""
|
|
431
459
|
await self.close()
|
|
432
460
|
|
|
@@ -482,10 +510,14 @@ class AsyncShotgun:
|
|
|
482
510
|
def _initialize(self, options: ConstructorOptions) -> None:
|
|
483
511
|
"""Initialize network-free state from validated constructor options."""
|
|
484
512
|
self._base_url = options.base_url.rstrip('/')
|
|
485
|
-
self._ca_certs =
|
|
513
|
+
self._ca_certs = (
|
|
514
|
+
options.ca_certs or os.environ.get('SHOTGUN_API_CACERTS') or None
|
|
515
|
+
if self._in_process
|
|
516
|
+
else config_module.constructor_ca_certs(options.ca_certs)
|
|
517
|
+
)
|
|
486
518
|
self._client_caps = ClientCapabilities()
|
|
487
519
|
self._connect_on_enter = options.connect
|
|
488
|
-
self._config = build_config(options)
|
|
520
|
+
self._config = build_config(options, in_process=self._in_process)
|
|
489
521
|
|
|
490
522
|
self._driver: AsyncDriver | None = None
|
|
491
523
|
self._driver_generation: int | None = None
|
|
@@ -546,7 +578,11 @@ class AsyncShotgun:
|
|
|
546
578
|
raise cancellation
|
|
547
579
|
|
|
548
580
|
def _new_transport(self, context: RequestContext) -> AsyncTransport:
|
|
549
|
-
"""Create
|
|
581
|
+
"""Create a transport from one context; mockgun overrides this package-internal hook."""
|
|
582
|
+
if context.in_process:
|
|
583
|
+
raise RuntimeError('an in-process client must supply its own transport')
|
|
584
|
+
if context.pool_maxsize < 1:
|
|
585
|
+
raise ValueError('pool_maxsize must be at least 1')
|
|
550
586
|
module = importlib.import_module(f'{__package__}._async_httpx2')
|
|
551
587
|
factory = cast(
|
|
552
588
|
'type[Httpx2Transport]',
|
|
@@ -557,6 +593,8 @@ class AsyncShotgun:
|
|
|
557
593
|
proxy_headers=dict(context.proxy_headers) or None,
|
|
558
594
|
proxy_url=context.proxy_url,
|
|
559
595
|
pool_maxsize=context.pool_maxsize,
|
|
596
|
+
max_connection_idle_secs=context.max_connection_idle_secs,
|
|
597
|
+
tcp_keepalive_timeout_secs=context.tcp_keepalive_timeout_secs,
|
|
560
598
|
)
|
|
561
599
|
|
|
562
600
|
def _acquire_driver(
|
|
@@ -566,7 +604,14 @@ class AsyncShotgun:
|
|
|
566
604
|
"""Lease one generation's driver and return idle transports to close."""
|
|
567
605
|
close_after_unlock: list[AsyncTransport] = []
|
|
568
606
|
with self._transport_lock:
|
|
569
|
-
|
|
607
|
+
decision = _files.choose_driver_lease(
|
|
608
|
+
self._known_transport,
|
|
609
|
+
self._transport,
|
|
610
|
+
self._driver_generation,
|
|
611
|
+
context.transport_generation,
|
|
612
|
+
self._transport_owned,
|
|
613
|
+
)
|
|
614
|
+
if decision.known_changed:
|
|
570
615
|
if self._known_transport is not None:
|
|
571
616
|
retired = retire_transport(
|
|
572
617
|
self._lease_counts, self._retired_transports, self._known_transport
|
|
@@ -584,16 +629,14 @@ class AsyncShotgun:
|
|
|
584
629
|
self._transport = transport
|
|
585
630
|
self._known_transport = transport
|
|
586
631
|
self._transport_owned = True
|
|
587
|
-
elif
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
self._transport = transport
|
|
596
|
-
self._known_transport = transport
|
|
632
|
+
elif decision.replace_owned_transport:
|
|
633
|
+
retired = retire_transport(self._lease_counts, self._retired_transports, transport)
|
|
634
|
+
if retired is not None:
|
|
635
|
+
close_after_unlock.append(retired)
|
|
636
|
+
transport = self._new_transport(context)
|
|
637
|
+
self._transport = transport
|
|
638
|
+
self._known_transport = transport
|
|
639
|
+
if decision.reset_driver:
|
|
597
640
|
self._driver = None
|
|
598
641
|
|
|
599
642
|
if self._driver is None:
|
|
@@ -951,7 +994,9 @@ class AsyncShotgun:
|
|
|
951
994
|
driver,
|
|
952
995
|
context,
|
|
953
996
|
exchange,
|
|
954
|
-
lambda text, _headers: _files.parse_link_completion(
|
|
997
|
+
lambda text, _headers: _files.parse_link_completion(
|
|
998
|
+
text, operation='share_thumbnail', entities=entities
|
|
999
|
+
),
|
|
955
1000
|
)
|
|
956
1001
|
|
|
957
1002
|
async def _thumbnail_source_ready(
|
|
@@ -1025,7 +1070,7 @@ class AsyncShotgun:
|
|
|
1025
1070
|
_files.StorageRange(
|
|
1026
1071
|
length=prepared.file_size,
|
|
1027
1072
|
offset=0,
|
|
1028
|
-
path=prepared.
|
|
1073
|
+
path=prepared.path,
|
|
1029
1074
|
),
|
|
1030
1075
|
chunk_size=_ASYNC_FILE_BATCH_SIZE,
|
|
1031
1076
|
)
|
|
@@ -1062,7 +1107,16 @@ class AsyncShotgun:
|
|
|
1062
1107
|
driver,
|
|
1063
1108
|
context,
|
|
1064
1109
|
exchange,
|
|
1065
|
-
lambda text, _headers: _files.parse_link_completion(
|
|
1110
|
+
lambda text, _headers: _files.parse_link_completion(
|
|
1111
|
+
text,
|
|
1112
|
+
operation='upload link',
|
|
1113
|
+
entities=[
|
|
1114
|
+
{
|
|
1115
|
+
'type': prepared.options.arguments.entity_type,
|
|
1116
|
+
'id': prepared.options.arguments.entity_id,
|
|
1117
|
+
}
|
|
1118
|
+
],
|
|
1119
|
+
),
|
|
1066
1120
|
)
|
|
1067
1121
|
|
|
1068
1122
|
@staticmethod
|
|
@@ -1135,17 +1189,9 @@ class AsyncShotgun:
|
|
|
1135
1189
|
prepared: _files.PreparedUpload,
|
|
1136
1190
|
) -> int:
|
|
1137
1191
|
"""Execute one prepared upload through a captured driver lease."""
|
|
1138
|
-
arguments = prepared.options.arguments
|
|
1139
1192
|
capabilities = await self._ensure_readiness(context, driver)
|
|
1140
1193
|
operation_context = await self._authenticated_context(context, driver)
|
|
1141
|
-
upload_path = _files.
|
|
1142
|
-
_files.choose_upload_path(
|
|
1143
|
-
capabilities.server_info,
|
|
1144
|
-
arguments.entity_type,
|
|
1145
|
-
arguments.field_name,
|
|
1146
|
-
prepared.file_size,
|
|
1147
|
-
)
|
|
1148
|
-
)
|
|
1194
|
+
upload_path = _files.choose_prepared_upload_path(capabilities.server_info, prepared)
|
|
1149
1195
|
multipart = upload_path == 'multipart'
|
|
1150
1196
|
upload_info = await self._upload_allocation(
|
|
1151
1197
|
driver,
|
|
@@ -1185,14 +1231,8 @@ class AsyncShotgun:
|
|
|
1185
1231
|
) -> int:
|
|
1186
1232
|
"""Execute the exact direct-storage thumbnail workflow."""
|
|
1187
1233
|
capabilities = await self._ensure_readiness(context, driver)
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
_files.choose_upload_path(
|
|
1191
|
-
capabilities.server_info,
|
|
1192
|
-
arguments.entity_type,
|
|
1193
|
-
arguments.field_name,
|
|
1194
|
-
prepared.file_size,
|
|
1195
|
-
)
|
|
1234
|
+
_files.choose_prepared_upload_path(
|
|
1235
|
+
capabilities.server_info, prepared, sharing_thumbnail=True
|
|
1196
1236
|
)
|
|
1197
1237
|
upload_info = await self._upload_allocation(
|
|
1198
1238
|
driver,
|
|
@@ -1225,10 +1265,6 @@ class AsyncShotgun:
|
|
|
1225
1265
|
result.update({field_name: current.get(field_name) for field_name in thumbnails})
|
|
1226
1266
|
return result
|
|
1227
1267
|
|
|
1228
|
-
@overload
|
|
1229
|
-
async def activity_stream_read(self, entity_type: Never) -> dict[str, object]: ...
|
|
1230
|
-
|
|
1231
|
-
@overload
|
|
1232
1268
|
async def activity_stream_read(
|
|
1233
1269
|
self,
|
|
1234
1270
|
entity_type: str,
|
|
@@ -1237,34 +1273,30 @@ class AsyncShotgun:
|
|
|
1237
1273
|
min_id: int | None = None,
|
|
1238
1274
|
max_id: int | None = None,
|
|
1239
1275
|
limit: int | None = None,
|
|
1240
|
-
) -> dict[str, object]: ...
|
|
1241
|
-
|
|
1242
|
-
@_methods.expose_activity_stream_read_signature
|
|
1243
|
-
async def activity_stream_read(
|
|
1244
|
-
self,
|
|
1245
|
-
*args: object,
|
|
1246
|
-
**kwargs: object,
|
|
1247
1276
|
) -> dict[str, object]:
|
|
1248
1277
|
"""Read the activity stream for one entity.
|
|
1249
1278
|
|
|
1250
1279
|
Args:
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1280
|
+
entity_type: Entity type whose activity stream to read.
|
|
1281
|
+
entity_id: Numeric identifier of the entity.
|
|
1282
|
+
entity_fields: Optional fields to return for linked entity types.
|
|
1283
|
+
min_id: Optional minimum activity identifier.
|
|
1284
|
+
max_id: Optional maximum activity identifier.
|
|
1285
|
+
limit: Optional maximum number of activity records.
|
|
1255
1286
|
|
|
1256
1287
|
Returns:
|
|
1257
1288
|
Activity stream data returned by the server.
|
|
1258
1289
|
|
|
1259
1290
|
Raises:
|
|
1260
|
-
TypeError: If arguments do not match the documented signature.
|
|
1261
1291
|
ShotgunError: If an RPC fault or terminal transport failure occurs,
|
|
1262
1292
|
or if the server result has the wrong type.
|
|
1263
1293
|
ProtocolError: If a server request returns an HTTP error.
|
|
1264
1294
|
ValueError: If authenticated HTTP violates the configured origin
|
|
1265
1295
|
policy.
|
|
1266
1296
|
"""
|
|
1267
|
-
options = _methods.
|
|
1297
|
+
options = _methods.ActivityStreamReadOptions(
|
|
1298
|
+
entity_type, entity_id, entity_fields, min_id, max_id, limit
|
|
1299
|
+
)
|
|
1268
1300
|
result = await self._call(
|
|
1269
1301
|
'activity_stream',
|
|
1270
1302
|
_methods.build_activity_stream_read_params(options),
|
|
@@ -1348,7 +1380,10 @@ class AsyncShotgun:
|
|
|
1348
1380
|
type, or if the server result is not a list.
|
|
1349
1381
|
ProtocolError: If a server request returns an HTTP error.
|
|
1350
1382
|
ValueError: If authenticated HTTP violates the configured origin
|
|
1351
|
-
policy.
|
|
1383
|
+
policy, or a naive datetime cannot be converted to UTC.
|
|
1384
|
+
OverflowError: If converting an aware datetime to UTC exceeds the
|
|
1385
|
+
supported datetime range.
|
|
1386
|
+
OSError: If the platform cannot resolve a local datetime.
|
|
1352
1387
|
"""
|
|
1353
1388
|
calls = _crud.build_batch_calls(requests)
|
|
1354
1389
|
if not calls:
|
|
@@ -1425,7 +1460,15 @@ class AsyncShotgun:
|
|
|
1425
1460
|
)
|
|
1426
1461
|
|
|
1427
1462
|
async def close(self) -> None:
|
|
1428
|
-
"""Swap out and drain the current transport without aborting work.
|
|
1463
|
+
"""Swap out and drain the current transport without aborting work.
|
|
1464
|
+
|
|
1465
|
+
Attempt cleanup of the remaining retired transports if one close fails,
|
|
1466
|
+
then propagate the first failure.
|
|
1467
|
+
|
|
1468
|
+
Raises:
|
|
1469
|
+
ShotgunError: If the built-in transport cannot finish cleanup.
|
|
1470
|
+
Exception: If a replacement transport's cleanup fails.
|
|
1471
|
+
"""
|
|
1429
1472
|
close_after_unlock: list[AsyncTransport] = []
|
|
1430
1473
|
with self._transport_lock:
|
|
1431
1474
|
advance_transport_generation(self._config)
|
|
@@ -1544,7 +1587,10 @@ class AsyncShotgun:
|
|
|
1544
1587
|
file. A thumbnail failure occurs after the entity is created.
|
|
1545
1588
|
ProtocolError: If a server request returns an HTTP error.
|
|
1546
1589
|
ValueError: If authenticated HTTP violates the configured origin
|
|
1547
|
-
policy.
|
|
1590
|
+
policy, or a naive datetime cannot be converted to UTC.
|
|
1591
|
+
OverflowError: If converting an aware datetime to UTC exceeds the
|
|
1592
|
+
supported datetime range.
|
|
1593
|
+
OSError: If the platform cannot resolve a local datetime.
|
|
1548
1594
|
"""
|
|
1549
1595
|
field_data, thumbnails = _files.split_thumbnail_uploads(data, keep_image_none=False)
|
|
1550
1596
|
params = _crud.build_create_params(entity_type, field_data, return_fields)
|
|
@@ -1612,13 +1658,13 @@ class AsyncShotgun:
|
|
|
1612
1658
|
|
|
1613
1659
|
Raises:
|
|
1614
1660
|
TypeError: If no attachment is given or the reference is a string.
|
|
1615
|
-
ValueError: If an entity dictionary references a different type
|
|
1661
|
+
ValueError: If an entity dictionary references a different type,
|
|
1662
|
+
server settings are incomplete, or authenticated HTTP violates
|
|
1663
|
+
the configured origin security policy.
|
|
1616
1664
|
ShotgunError: If another attachment reference is unsupported or a
|
|
1617
1665
|
setup RPC reports a fault; the fault keeps its specific type.
|
|
1618
1666
|
ShotgunFileDownloadError: If the resolved URL is invalid or setup,
|
|
1619
1667
|
transfer, or destination I/O fails.
|
|
1620
|
-
ValueError: If server settings are incomplete or authenticated HTTP
|
|
1621
|
-
violates the configured origin security policy.
|
|
1622
1668
|
"""
|
|
1623
1669
|
if attachment is False:
|
|
1624
1670
|
if attachment_id is None:
|
|
@@ -1653,7 +1699,7 @@ class AsyncShotgun:
|
|
|
1653
1699
|
headers=headers,
|
|
1654
1700
|
url=url,
|
|
1655
1701
|
)
|
|
1656
|
-
return await exchange.
|
|
1702
|
+
return await exchange.execute(driver, operation)
|
|
1657
1703
|
except _errors.ShotgunFileDownloadError:
|
|
1658
1704
|
raise
|
|
1659
1705
|
except (TransportError, _errors.ProtocolError, OSError):
|
|
@@ -1668,57 +1714,39 @@ class AsyncShotgun:
|
|
|
1668
1714
|
"""
|
|
1669
1715
|
raise _methods.entity_types_alias_error()
|
|
1670
1716
|
|
|
1671
|
-
@overload
|
|
1672
1717
|
async def export_page(
|
|
1673
1718
|
self,
|
|
1674
1719
|
page_id: int,
|
|
1675
|
-
|
|
1676
|
-
/,
|
|
1720
|
+
format: str, # noqa: A002
|
|
1677
1721
|
layout_name: str | None = None,
|
|
1678
|
-
) -> str:
|
|
1679
|
-
|
|
1680
|
-
@overload
|
|
1681
|
-
async def export_page(
|
|
1682
|
-
self,
|
|
1683
|
-
page_id: int,
|
|
1684
|
-
**kwargs: Unpack[_methods.ExportPageKeywordArguments],
|
|
1685
|
-
) -> str: ...
|
|
1686
|
-
|
|
1687
|
-
@_methods.expose_export_page_signature
|
|
1688
|
-
async def export_page(self, *args: object, **kwargs: object) -> str:
|
|
1722
|
+
) -> str:
|
|
1689
1723
|
"""Export a page using the documented arguments.
|
|
1690
1724
|
|
|
1691
1725
|
The Page or its layout must be marked API Exportable in the web UI, or
|
|
1692
1726
|
the server rejects the export.
|
|
1693
1727
|
|
|
1694
1728
|
Args:
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
signature.
|
|
1729
|
+
page_id: Numeric page identifier.
|
|
1730
|
+
format: Required nonempty export format name.
|
|
1731
|
+
layout_name: Optional layout name, including an empty name.
|
|
1699
1732
|
|
|
1700
1733
|
Returns:
|
|
1701
1734
|
Exported page text returned by the server.
|
|
1702
1735
|
|
|
1703
1736
|
Raises:
|
|
1704
|
-
TypeError: If arguments do not match the documented signature.
|
|
1705
1737
|
ShotgunError: If an RPC fault or terminal transport failure occurs,
|
|
1706
|
-
or if
|
|
1738
|
+
or if `page_id`, `format`, or the server result is invalid.
|
|
1707
1739
|
ProtocolError: If a server request returns an HTTP error.
|
|
1708
1740
|
ValueError: If authenticated HTTP violates the configured origin
|
|
1709
1741
|
policy.
|
|
1710
1742
|
"""
|
|
1711
|
-
options = _methods.
|
|
1743
|
+
options = _methods.ExportPageOptions(page_id, format, layout_name)
|
|
1712
1744
|
result = await self._call(
|
|
1713
1745
|
'export_page',
|
|
1714
1746
|
_methods.build_export_page_params(options),
|
|
1715
1747
|
)
|
|
1716
1748
|
return _methods.require_string_result(result, 'export_page')
|
|
1717
1749
|
|
|
1718
|
-
@overload
|
|
1719
|
-
async def find(self, entity_type: Never) -> list[dict[str, object]]: ...
|
|
1720
|
-
|
|
1721
|
-
@overload
|
|
1722
1750
|
async def find(
|
|
1723
1751
|
self,
|
|
1724
1752
|
entity_type: str,
|
|
@@ -1731,33 +1759,50 @@ class AsyncShotgun:
|
|
|
1731
1759
|
page: int = 0,
|
|
1732
1760
|
include_archived_projects: bool = True,
|
|
1733
1761
|
additional_filter_presets: object = None,
|
|
1734
|
-
) -> list[dict[str, object]]:
|
|
1735
|
-
|
|
1736
|
-
@_crud.expose_find_signature
|
|
1737
|
-
async def find(self, *args: object, **kwargs: object) -> list[dict[str, object]]:
|
|
1762
|
+
) -> list[dict[str, object]]:
|
|
1738
1763
|
"""Find entities matching the supplied filters.
|
|
1739
1764
|
|
|
1740
1765
|
Args:
|
|
1741
|
-
|
|
1742
|
-
|
|
1766
|
+
entity_type: Entity type to find.
|
|
1767
|
+
filters: Filters in list or dict syntax.
|
|
1768
|
+
fields: Optional fields to return; defaults to `id`.
|
|
1769
|
+
order: Optional field and direction sort specifications.
|
|
1770
|
+
filter_operator: `'all'` or `'any'` for list-syntax filters.
|
|
1771
|
+
limit: Maximum entities to return, or `0` for no limit.
|
|
1772
|
+
retired_only: Return retired entities instead of active entities.
|
|
1773
|
+
page: Specific page to return, or `0` to read all pages.
|
|
1774
|
+
include_archived_projects: Include entities from archived projects.
|
|
1775
|
+
additional_filter_presets: Optional server-side filter presets.
|
|
1743
1776
|
|
|
1744
1777
|
Returns:
|
|
1745
1778
|
Matching entities in server order.
|
|
1746
1779
|
|
|
1747
1780
|
Raises:
|
|
1748
|
-
TypeError: If arguments do not match the documented signature.
|
|
1749
1781
|
ShotgunError: If an RPC fault or terminal transport failure occurs,
|
|
1750
|
-
or if filters, paging, ordering, or a server result is
|
|
1782
|
+
or if filters, fields, paging, ordering, or a server result is
|
|
1783
|
+
invalid.
|
|
1751
1784
|
ProtocolError: If a server request returns an HTTP error.
|
|
1752
1785
|
ValueError: If authenticated HTTP violates the configured origin
|
|
1753
|
-
policy.
|
|
1786
|
+
policy, or a naive datetime cannot be converted to UTC.
|
|
1787
|
+
OverflowError: If converting an aware datetime to UTC exceeds the
|
|
1788
|
+
supported datetime range.
|
|
1789
|
+
OSError: If the platform cannot resolve a local datetime.
|
|
1754
1790
|
"""
|
|
1755
|
-
return await self._find(
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1791
|
+
return await self._find(
|
|
1792
|
+
_crud.FindOptions(
|
|
1793
|
+
entity_type,
|
|
1794
|
+
filters,
|
|
1795
|
+
fields,
|
|
1796
|
+
order,
|
|
1797
|
+
filter_operator,
|
|
1798
|
+
limit,
|
|
1799
|
+
retired_only,
|
|
1800
|
+
page,
|
|
1801
|
+
include_archived_projects,
|
|
1802
|
+
additional_filter_presets,
|
|
1803
|
+
)
|
|
1804
|
+
)
|
|
1759
1805
|
|
|
1760
|
-
@overload
|
|
1761
1806
|
async def find_one(
|
|
1762
1807
|
self,
|
|
1763
1808
|
entity_type: str,
|
|
@@ -1768,28 +1813,43 @@ class AsyncShotgun:
|
|
|
1768
1813
|
retired_only: bool = False,
|
|
1769
1814
|
include_archived_projects: bool = True,
|
|
1770
1815
|
additional_filter_presets: object = None,
|
|
1771
|
-
) -> dict[str, object] | None:
|
|
1772
|
-
|
|
1773
|
-
@_crud.expose_find_one_signature
|
|
1774
|
-
async def find_one(self, *args: object, **kwargs: object) -> dict[str, object] | None:
|
|
1816
|
+
) -> dict[str, object] | None:
|
|
1775
1817
|
"""Return the first entity matching the supplied filters.
|
|
1776
1818
|
|
|
1777
1819
|
Args:
|
|
1778
|
-
|
|
1779
|
-
|
|
1820
|
+
entity_type: Entity type to find.
|
|
1821
|
+
filters: Filters in list or dict syntax.
|
|
1822
|
+
fields: Optional fields to return; defaults to `id`.
|
|
1823
|
+
order: Optional field and direction sort specifications.
|
|
1824
|
+
filter_operator: `'all'` or `'any'` for list-syntax filters.
|
|
1825
|
+
retired_only: Return a retired entity instead of an active entity.
|
|
1826
|
+
include_archived_projects: Include entities from archived projects.
|
|
1827
|
+
additional_filter_presets: Optional server-side filter presets.
|
|
1780
1828
|
|
|
1781
1829
|
Returns:
|
|
1782
1830
|
The first matching entity, or `None` when no entity matches.
|
|
1783
1831
|
|
|
1784
1832
|
Raises:
|
|
1785
|
-
TypeError: If arguments do not match the documented signature.
|
|
1786
1833
|
ShotgunError: If an RPC fault or terminal transport failure occurs,
|
|
1787
|
-
or if filters, paging, ordering, or a server result is
|
|
1834
|
+
or if filters, fields, paging, ordering, or a server result is
|
|
1835
|
+
invalid.
|
|
1788
1836
|
ProtocolError: If a server request returns an HTTP error.
|
|
1789
1837
|
ValueError: If authenticated HTTP violates the configured origin
|
|
1790
|
-
policy.
|
|
1838
|
+
policy, or a naive datetime cannot be converted to UTC.
|
|
1839
|
+
OverflowError: If converting an aware datetime to UTC exceeds the
|
|
1840
|
+
supported datetime range.
|
|
1841
|
+
OSError: If the platform cannot resolve a local datetime.
|
|
1791
1842
|
"""
|
|
1792
|
-
options = _crud.
|
|
1843
|
+
options = _crud.FindOneOptions(
|
|
1844
|
+
entity_type,
|
|
1845
|
+
filters,
|
|
1846
|
+
fields,
|
|
1847
|
+
order,
|
|
1848
|
+
filter_operator,
|
|
1849
|
+
retired_only,
|
|
1850
|
+
include_archived_projects,
|
|
1851
|
+
additional_filter_presets,
|
|
1852
|
+
)
|
|
1793
1853
|
return _crud.first_result(await self._find(_crud.find_options_from_one(options)))
|
|
1794
1854
|
|
|
1795
1855
|
async def follow(
|
|
@@ -2186,7 +2246,10 @@ class AsyncShotgun:
|
|
|
2186
2246
|
or if the server result is invalid.
|
|
2187
2247
|
ProtocolError: If a server request returns an HTTP error.
|
|
2188
2248
|
ValueError: If authenticated HTTP violates the configured origin
|
|
2189
|
-
policy.
|
|
2249
|
+
policy, or a naive datetime cannot be converted to UTC.
|
|
2250
|
+
OverflowError: If converting an aware datetime to UTC exceeds the
|
|
2251
|
+
supported datetime range.
|
|
2252
|
+
OSError: If the platform cannot resolve a local datetime.
|
|
2190
2253
|
"""
|
|
2191
2254
|
params = _methods.build_schema_field_create_params(
|
|
2192
2255
|
entity_type,
|
|
@@ -2274,7 +2337,10 @@ class AsyncShotgun:
|
|
|
2274
2337
|
or if the server result is invalid.
|
|
2275
2338
|
ProtocolError: If a server request returns an HTTP error.
|
|
2276
2339
|
ValueError: If authenticated HTTP violates the configured origin
|
|
2277
|
-
policy.
|
|
2340
|
+
policy, or a naive datetime cannot be converted to UTC.
|
|
2341
|
+
OverflowError: If converting an aware datetime to UTC exceeds the
|
|
2342
|
+
supported datetime range.
|
|
2343
|
+
OSError: If the platform cannot resolve a local datetime.
|
|
2278
2344
|
"""
|
|
2279
2345
|
params = _methods.build_schema_field_update_params(
|
|
2280
2346
|
entity_type,
|
|
@@ -2389,30 +2455,26 @@ class AsyncShotgun:
|
|
|
2389
2455
|
source_entity,
|
|
2390
2456
|
filmstrip_thumbnail,
|
|
2391
2457
|
)
|
|
2392
|
-
if share.thumbnail_path is not None and 'field_name' in kwargs:
|
|
2393
|
-
raise TypeError("AsyncShotgun.upload() got multiple values for argument 'field_name'")
|
|
2394
2458
|
prepared: _files.PreparedUpload | None = None
|
|
2395
2459
|
if share.thumbnail_path is not None:
|
|
2396
|
-
|
|
2397
|
-
(
|
|
2398
|
-
|
|
2399
|
-
|
|
2400
|
-
|
|
2401
|
-
|
|
2402
|
-
|
|
2403
|
-
|
|
2404
|
-
|
|
2405
|
-
|
|
2406
|
-
|
|
2407
|
-
|
|
2460
|
+
for keyword in kwargs:
|
|
2461
|
+
if keyword not in ('display_name', 'tag_list', 'created_at'):
|
|
2462
|
+
raise TypeError(
|
|
2463
|
+
'AsyncShotgun.share_thumbnail() got an unexpected keyword '
|
|
2464
|
+
f"argument '{keyword}'"
|
|
2465
|
+
)
|
|
2466
|
+
arguments = _files.UploadArguments(
|
|
2467
|
+
entity_type=share.source_entity_type,
|
|
2468
|
+
entity_id=share.source_entity_id,
|
|
2469
|
+
path=share.thumbnail_path,
|
|
2470
|
+
field_name=_files.thumbnail_fields(filmstrip_thumbnail)[0],
|
|
2471
|
+
display_name=cast('str | None', kwargs.get('display_name')),
|
|
2472
|
+
tag_list=cast('str | None', kwargs.get('tag_list')),
|
|
2473
|
+
created_at=cast('_dt_module.datetime | None', kwargs.get('created_at')),
|
|
2408
2474
|
)
|
|
2409
2475
|
prepared = await asyncio.to_thread(
|
|
2410
2476
|
_files.prepare_upload,
|
|
2411
|
-
_files.
|
|
2412
|
-
arguments=arguments,
|
|
2413
|
-
upload_type='Thumbnail',
|
|
2414
|
-
filmstrip=filmstrip_thumbnail,
|
|
2415
|
-
),
|
|
2477
|
+
_files.upload_options(arguments),
|
|
2416
2478
|
)
|
|
2417
2479
|
context = config_module.snapshot(self._config)
|
|
2418
2480
|
require_secure_rpc(context, include_auth=True)
|
|
@@ -2427,9 +2489,7 @@ class AsyncShotgun:
|
|
|
2427
2489
|
)
|
|
2428
2490
|
if len(share.entities) == 1:
|
|
2429
2491
|
return attachment_id
|
|
2430
|
-
field_name
|
|
2431
|
-
'filmstrip_image' if filmstrip_thumbnail else 'image'
|
|
2432
|
-
)
|
|
2492
|
+
field_name = _files.thumbnail_fields(filmstrip_thumbnail)[1]
|
|
2433
2493
|
ready = await driver.poll(
|
|
2434
2494
|
lambda: self._thumbnail_source_ready(
|
|
2435
2495
|
driver,
|
|
@@ -2458,10 +2518,6 @@ class AsyncShotgun:
|
|
|
2458
2518
|
filmstrip_thumbnail,
|
|
2459
2519
|
)
|
|
2460
2520
|
|
|
2461
|
-
@overload
|
|
2462
|
-
async def summarize(self, entity_type: Never) -> dict[str, object]: ...
|
|
2463
|
-
|
|
2464
|
-
@overload
|
|
2465
2521
|
async def summarize(
|
|
2466
2522
|
self,
|
|
2467
2523
|
entity_type: str,
|
|
@@ -2470,28 +2526,39 @@ class AsyncShotgun:
|
|
|
2470
2526
|
filter_operator: str | None = None,
|
|
2471
2527
|
grouping: list[dict[str, Any]] | None = None,
|
|
2472
2528
|
include_archived_projects: bool = True,
|
|
2473
|
-
) -> dict[str, object]:
|
|
2474
|
-
|
|
2475
|
-
@_methods.expose_summarize_signature
|
|
2476
|
-
async def summarize(self, *args: object, **kwargs: object) -> dict[str, object]:
|
|
2529
|
+
) -> dict[str, object]:
|
|
2477
2530
|
"""Summarize field data returned by a query.
|
|
2478
2531
|
|
|
2479
2532
|
Args:
|
|
2480
|
-
|
|
2481
|
-
|
|
2533
|
+
entity_type: Entity type to summarize.
|
|
2534
|
+
filters: Filters in list or dict syntax.
|
|
2535
|
+
summary_fields: List of `{'field': ..., 'type': ...}` summaries.
|
|
2536
|
+
filter_operator: `'all'` or `'any'` for list-syntax filters.
|
|
2537
|
+
grouping: Optional grouping specs.
|
|
2538
|
+
include_archived_projects: Include archived projects when `True`.
|
|
2482
2539
|
|
|
2483
2540
|
Returns:
|
|
2484
|
-
Dict with `summaries` and
|
|
2541
|
+
Dict with `summaries` and (when grouped) `groups`.
|
|
2485
2542
|
|
|
2486
2543
|
Raises:
|
|
2487
|
-
TypeError: If arguments do not match the documented signature.
|
|
2488
2544
|
ShotgunError: If an RPC fault or terminal transport failure occurs,
|
|
2489
|
-
or if
|
|
2545
|
+
or if `filters`, `summary_fields`, or the server result has the
|
|
2546
|
+
wrong type.
|
|
2490
2547
|
ProtocolError: If a server request returns an HTTP error.
|
|
2491
2548
|
ValueError: If authenticated HTTP violates the configured origin
|
|
2492
|
-
policy.
|
|
2549
|
+
policy, or a naive datetime cannot be converted to UTC.
|
|
2550
|
+
OverflowError: If converting an aware datetime to UTC exceeds the
|
|
2551
|
+
supported datetime range.
|
|
2552
|
+
OSError: If the platform cannot resolve a local datetime.
|
|
2493
2553
|
"""
|
|
2494
|
-
options = _methods.
|
|
2554
|
+
options = _methods.SummarizeOptions(
|
|
2555
|
+
entity_type,
|
|
2556
|
+
filters,
|
|
2557
|
+
summary_fields,
|
|
2558
|
+
filter_operator,
|
|
2559
|
+
grouping,
|
|
2560
|
+
include_archived_projects,
|
|
2561
|
+
)
|
|
2495
2562
|
params = _methods.build_summarize_params(options)
|
|
2496
2563
|
result = await self._call('summarize', params)
|
|
2497
2564
|
return _crud.require_dictionary_result(result, 'summarize')
|
|
@@ -2519,7 +2586,10 @@ class AsyncShotgun:
|
|
|
2519
2586
|
or if arguments or the server result are invalid.
|
|
2520
2587
|
ProtocolError: If a server request returns an HTTP error.
|
|
2521
2588
|
ValueError: If authenticated HTTP violates the configured origin
|
|
2522
|
-
policy.
|
|
2589
|
+
policy, or a naive datetime cannot be converted to UTC.
|
|
2590
|
+
OverflowError: If converting an aware datetime to UTC exceeds the
|
|
2591
|
+
supported datetime range.
|
|
2592
|
+
OSError: If the platform cannot resolve a local datetime.
|
|
2523
2593
|
"""
|
|
2524
2594
|
params = _methods.build_text_search_params(
|
|
2525
2595
|
text,
|
|
@@ -2589,7 +2659,10 @@ class AsyncShotgun:
|
|
|
2589
2659
|
nonempty file. A thumbnail failure occurs after the update.
|
|
2590
2660
|
ProtocolError: If a server request returns an HTTP error.
|
|
2591
2661
|
ValueError: If authenticated HTTP violates the configured origin
|
|
2592
|
-
policy.
|
|
2662
|
+
policy, or a naive datetime cannot be converted to UTC.
|
|
2663
|
+
OverflowError: If converting an aware datetime to UTC exceeds the
|
|
2664
|
+
supported datetime range.
|
|
2665
|
+
OSError: If the platform cannot resolve a local datetime.
|
|
2593
2666
|
"""
|
|
2594
2667
|
field_data, thumbnails = _files.split_thumbnail_uploads(data, keep_image_none=True)
|
|
2595
2668
|
params = _crud.build_update_params(
|
|
@@ -2620,7 +2693,8 @@ class AsyncShotgun:
|
|
|
2620
2693
|
user: Optional user entity to attribute the access to.
|
|
2621
2694
|
|
|
2622
2695
|
Raises:
|
|
2623
|
-
ShotgunError: If
|
|
2696
|
+
ShotgunError: If a project or user ID is invalid, or an RPC fault
|
|
2697
|
+
or terminal transport failure occurs.
|
|
2624
2698
|
ProtocolError: If a server request returns an HTTP error.
|
|
2625
2699
|
ValueError: If authenticated HTTP violates the configured origin
|
|
2626
2700
|
policy.
|
|
@@ -2631,10 +2705,6 @@ class AsyncShotgun:
|
|
|
2631
2705
|
_InvocationOptions(logical_method_name='update_project_last_accessed'),
|
|
2632
2706
|
)
|
|
2633
2707
|
|
|
2634
|
-
@overload
|
|
2635
|
-
async def upload(self, entity_type: Never) -> int: ...
|
|
2636
|
-
|
|
2637
|
-
@overload
|
|
2638
2708
|
async def upload(
|
|
2639
2709
|
self,
|
|
2640
2710
|
entity_type: str,
|
|
@@ -2644,24 +2714,26 @@ class AsyncShotgun:
|
|
|
2644
2714
|
display_name: str | None = None,
|
|
2645
2715
|
tag_list: str | None = None,
|
|
2646
2716
|
created_at: _dt_module.datetime | None = None,
|
|
2647
|
-
) -> int:
|
|
2648
|
-
|
|
2649
|
-
@_expose_upload_signature
|
|
2650
|
-
async def upload(self, *args: object, **kwargs: object) -> int:
|
|
2717
|
+
) -> int:
|
|
2651
2718
|
"""Upload a file or thumbnail and return its Attachment ID.
|
|
2652
2719
|
|
|
2653
2720
|
A server requiring legacy form uploads raises `ShotgunError` before any
|
|
2654
2721
|
upload request is sent. Server capability reads may occur.
|
|
2655
2722
|
|
|
2656
2723
|
Args:
|
|
2657
|
-
|
|
2658
|
-
|
|
2724
|
+
entity_type: Entity type receiving the upload.
|
|
2725
|
+
entity_id: Numeric identifier of the receiving entity.
|
|
2726
|
+
path: Existing non-empty file path as text or filesystem-encoded
|
|
2727
|
+
bytes.
|
|
2728
|
+
field_name: Optional file field receiving an Attachment.
|
|
2729
|
+
display_name: Optional Attachment display name.
|
|
2730
|
+
tag_list: Optional comma-delimited Attachment tags.
|
|
2731
|
+
created_at: Optional Attachment creation timestamp.
|
|
2659
2732
|
|
|
2660
2733
|
Returns:
|
|
2661
2734
|
Created Attachment ID.
|
|
2662
2735
|
|
|
2663
2736
|
Raises:
|
|
2664
|
-
TypeError: If arguments do not match the documented signature.
|
|
2665
2737
|
ShotgunError: If the file or an upload response is invalid, an RPC
|
|
2666
2738
|
fault or terminal transport failure occurs, or the server
|
|
2667
2739
|
requires legacy form uploads.
|
|
@@ -2669,23 +2741,10 @@ class AsyncShotgun:
|
|
|
2669
2741
|
ValueError: If authenticated HTTP violates the configured origin
|
|
2670
2742
|
policy.
|
|
2671
2743
|
"""
|
|
2672
|
-
arguments =
|
|
2673
|
-
|
|
2674
|
-
'filmstrip_image',
|
|
2675
|
-
'filmstrip_thumb_image',
|
|
2676
|
-
'image',
|
|
2677
|
-
'thumb_image',
|
|
2678
|
-
}
|
|
2679
|
-
upload_type: Literal['Attachment', 'Thumbnail'] = (
|
|
2680
|
-
'Thumbnail' if arguments.field_name in thumbnail_fields else 'Attachment'
|
|
2681
|
-
)
|
|
2682
|
-
return await self._upload(
|
|
2683
|
-
_files.UploadOptions(
|
|
2684
|
-
arguments=arguments,
|
|
2685
|
-
upload_type=upload_type,
|
|
2686
|
-
filmstrip=arguments.field_name in {'filmstrip_image', 'filmstrip_thumb_image'},
|
|
2687
|
-
)
|
|
2744
|
+
arguments = _files.UploadArguments(
|
|
2745
|
+
entity_type, entity_id, path, field_name, display_name, tag_list, created_at
|
|
2688
2746
|
)
|
|
2747
|
+
return await self._upload(_files.upload_options(arguments))
|
|
2689
2748
|
|
|
2690
2749
|
async def upload_filmstrip_thumbnail(
|
|
2691
2750
|
self,
|
|
@@ -2845,10 +2904,6 @@ class AsyncShotgun:
|
|
|
2845
2904
|
result = await self._call('work_schedule_read', params)
|
|
2846
2905
|
return _crud.require_dictionary_result(result, 'work_schedule_read')
|
|
2847
2906
|
|
|
2848
|
-
@overload
|
|
2849
|
-
async def work_schedule_update(self, date: Never) -> dict[str, object]: ...
|
|
2850
|
-
|
|
2851
|
-
@overload
|
|
2852
2907
|
async def work_schedule_update(
|
|
2853
2908
|
self,
|
|
2854
2909
|
date: _dt_module.date | str,
|
|
@@ -2857,37 +2912,49 @@ class AsyncShotgun:
|
|
|
2857
2912
|
project: dict[str, Any] | None = None,
|
|
2858
2913
|
user: dict[str, Any] | None = None,
|
|
2859
2914
|
recalculate_field: str | None = None,
|
|
2860
|
-
) -> dict[str, object]: ...
|
|
2861
|
-
|
|
2862
|
-
@_methods.expose_work_schedule_update_signature
|
|
2863
|
-
async def work_schedule_update(
|
|
2864
|
-
self,
|
|
2865
|
-
*args: object,
|
|
2866
|
-
**kwargs: object,
|
|
2867
2915
|
) -> dict[str, object]:
|
|
2868
|
-
"""Update a work schedule
|
|
2916
|
+
"""Update a work schedule.
|
|
2869
2917
|
|
|
2870
2918
|
Args:
|
|
2871
|
-
|
|
2872
|
-
|
|
2919
|
+
date: Date or exact `YYYY-MM-DD` string to update.
|
|
2920
|
+
working: Whether the date is a working day.
|
|
2921
|
+
description: Optional schedule description.
|
|
2922
|
+
project: Optional project entity restriction.
|
|
2923
|
+
user: Optional user entity restriction.
|
|
2924
|
+
recalculate_field: Optional field to recalculate.
|
|
2873
2925
|
|
|
2874
2926
|
Returns:
|
|
2875
2927
|
Updated work schedule data returned by the server.
|
|
2876
2928
|
|
|
2877
2929
|
Raises:
|
|
2878
|
-
TypeError: If arguments do not match the documented signature.
|
|
2879
2930
|
ShotgunError: If an RPC fault or terminal transport failure occurs,
|
|
2880
|
-
or if
|
|
2931
|
+
or if the date, project/user exclusivity, or server result is
|
|
2932
|
+
invalid.
|
|
2881
2933
|
ProtocolError: If a server request returns an HTTP error.
|
|
2882
2934
|
ValueError: If authenticated HTTP violates the configured origin
|
|
2883
2935
|
policy.
|
|
2884
2936
|
"""
|
|
2885
|
-
options = _methods.
|
|
2937
|
+
options = _methods.WorkScheduleUpdateOptions(
|
|
2938
|
+
date, working, description, project, user, recalculate_field
|
|
2939
|
+
)
|
|
2886
2940
|
params = _methods.build_work_schedule_update_params(options)
|
|
2887
2941
|
result = await self._call('work_schedule_update', params)
|
|
2888
2942
|
return _crud.require_dictionary_result(result, 'work_schedule_update')
|
|
2889
2943
|
|
|
2890
2944
|
|
|
2945
|
+
async def _capture_download_cleanup(
|
|
2946
|
+
workers: _AsyncDownloadCancellationScope,
|
|
2947
|
+
function: Callable[_FilesystemParams, _FilesystemResult],
|
|
2948
|
+
*args: _FilesystemParams.args,
|
|
2949
|
+
**kwargs: _FilesystemParams.kwargs,
|
|
2950
|
+
) -> BaseException | None:
|
|
2951
|
+
"""Run one joined cleanup operation and capture its failure."""
|
|
2952
|
+
capture = _AsyncDownloadCleanupCapture(workers)
|
|
2953
|
+
with capture:
|
|
2954
|
+
await workers.run(function, *args, **kwargs)
|
|
2955
|
+
return capture.exception
|
|
2956
|
+
|
|
2957
|
+
|
|
2891
2958
|
def _open_download_temporary(destination: Path) -> tuple[ExitStack, BinaryIO, Path]:
|
|
2892
2959
|
"""Open one adjacent download temporary file."""
|
|
2893
2960
|
stack = ExitStack()
|
|
@@ -2920,7 +2987,6 @@ async def _execute_upload_exchange(
|
|
|
2920
2987
|
headers=dict(exchange.headers),
|
|
2921
2988
|
body=exchange.body_factory(),
|
|
2922
2989
|
timeout=context.timeout,
|
|
2923
|
-
token=uuid.uuid4().hex,
|
|
2924
2990
|
)
|
|
2925
2991
|
|
|
2926
2992
|
async def consume(
|